Error Reference
Error Response Envelope
All API errors follow a consistent envelope format based on RFC 9457 (Problem Details for HTTP APIs). Every error response contains these fields:
{
"type": "https://docs.aiffinity.me/developers/errors#invalid_schema",
"status": 400,
"title": "Invalid Schema",
"detail": "The 'temperature_c' field is declared as 'string' but the schema requires 'number'.",
"instance": "/v1/providers/packages/pkg_abc123/versions/ver_xyz789/validate"
}
| Field | Type | Description |
|---|---|---|
type |
string (URI) | A URI reference that identifies the error type. Links to the relevant section in this documentation. |
status |
integer | The HTTP status code. Matches the response status. |
title |
string | A short, human-readable summary of the error type. Stable across occurrences. |
detail |
string | A human-readable explanation specific to this occurrence of the problem. |
instance |
string (URI) | The API path that generated the error. Useful for debugging and support tickets. |
Some errors include additional fields beyond the standard envelope:
{
"type": "https://docs.aiffinity.me/developers/errors#missing_field",
"status": 400,
"title": "Missing Required Field",
"detail": "The field 'location' is required by the capability schema but was not provided.",
"instance": "/v1/providers/packages/pkg_abc123/capabilities/current_weather/execute",
"field": "location",
"schema_path": "$.properties.location"
}
Validation Errors (400)
Returned when the request payload, query parameters, or capability descriptor fails validation.
| Error Code | Status | Description |
|---|---|---|
| invalid_schema | 400 | The capability descriptor's JSON Schema is malformed or contains unsupported constructs. |
| missing_field | 400 | A required field is missing from the request body or capability response. |
| invalid_format | 400 | A field value does not match its expected format (e.g., invalid UUID, malformed URL, wrong date format). |
invalid_schema — Invalid Schema
Possible causes:
- Using a JSON Schema draft version other than draft-07 or 2020-12
- Circular
$refreferences in the schema - Unsupported keywords (e.g.,
if/then/elsein draft-07 mode) - Missing
typedeclaration on a property
Resolution:
- Validate your schema locally with
npx @aiffinity/provider-platform-sdk validate --local ./capability.json - Ensure all
$refreferences resolve to valid definitions - Stick to draft-07 for maximum compatibility
missing_field — Missing Required Field
Possible causes:
- A field marked as
requiredin the schema was not included in the request payload - A typo in the field name (field names are case-sensitive)
- The field was nested at the wrong level in the JSON structure
Resolution:
- Check the
fieldandschema_pathin the error response to identify exactly which field is missing - Verify the request payload structure matches the schema
invalid_format — Invalid Format
Possible causes:
- UUID fields not matching RFC 4122 format
- URL fields missing the scheme (
https://) - Date/time fields not in ISO 8601 format
- Enum values not matching the declared set
Resolution:
- Check the
detailmessage for the specific format requirement - UUIDs must be lowercase with hyphens:
a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d - URLs must include the protocol:
https://example.com/path
Authentication Errors (401/403)
Returned when authentication fails or the authenticated principal lacks permission for the requested operation.
| Error Code | Status | Description |
|---|---|---|
| token_expired | 401 | The access token has expired. Obtain a new token using your refresh token or client credentials. |
| insufficient_scope | 403 | The access token does not include the required scope for this operation. |
| account_suspended | 403 | Your developer account or the specific package has been suspended. Check the developer console for details. |
token_expired — Token Expired
Possible causes:
- Access tokens expire after 1 hour by default
- Clock skew between your server and Aiffinity's auth service
Resolution:
- Use the SDK's built-in token refresh: it automatically refreshes before expiry
- If using raw HTTP, call
POST /v1/auth/tokenwith your refresh token - Ensure your server clock is synchronized with NTP
// The SDK handles token refresh automatically
const client = new ProviderPlatformClient({
clientId: process.env.AIFFINITY_CLIENT_ID,
clientSecret: process.env.AIFFINITY_CLIENT_SECRET,
// Token refresh is automatic — no manual handling needed
});
// Manual refresh with raw HTTP
const response = await fetch('https://api.aiffinity.me/v1/auth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'client_credentials',
client_id: process.env.AIFFINITY_CLIENT_ID,
client_secret: process.env.AIFFINITY_CLIENT_SECRET,
}),
});
insufficient_scope — Insufficient Scope
Possible causes:
- The token was issued with a narrower scope than the operation requires
- The app registration does not include the required scope
- Attempting to access another developer's resources
Resolution:
- Check the
detailfield for the required scope (e.g.,packages:write,capabilities:execute) - Update your app's scope configuration in the developer console
- Request a new token after updating scopes
account_suspended — Account Suspended
Possible causes:
- Policy violation detected (see Suspension & Reinstatement)
- Security incident requiring immediate response
- Sustained reliability failure exceeding thresholds
Resolution:
- Log in to the developer console to review the suspension notice
- Address the cited issues and submit a remediation report
- Contact [email protected] if you need assistance
Rate Limit Errors (429)
Returned when your application exceeds the allowed request rate. All rate limit responses include Retry-After and X-RateLimit-* headers.
| Error Code | Status | Description |
|---|---|---|
| rate_limit_exceeded | 429 | You have exceeded the sustained request rate for your tier. Back off and retry after the Retry-After interval. |
| burst_limit | 429 | Too many requests in a short burst window (typically 10 requests per second). Spread requests more evenly. |
rate_limit_exceeded — Rate Limit Exceeded
Possible causes:
- Exceeding your tier's per-minute request quota (default: 600 requests/minute for standard tier)
- Running conformance tests in a tight loop without delays
- Multiple CI pipelines hitting the API concurrently
Resolution:
- Respect the
Retry-Afterheader value (in seconds) - Implement exponential backoff with jitter
- Use the rate limit headers to track remaining quota:
X-RateLimit-Remaining,X-RateLimit-Reset
// Example: reading rate limit headers
const response = await fetch('https://api.aiffinity.me/v1/providers/packages', {
headers: { 'Authorization': `Bearer ${token}` },
});
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After') || '60', 10);
console.log(`Rate limited. Retrying in ${retryAfter}s`);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
}
// Proactive quota tracking
const remaining = parseInt(response.headers.get('X-RateLimit-Remaining') || '0', 10);
const resetAt = new Date(parseInt(response.headers.get('X-RateLimit-Reset') || '0', 10) * 1000);
console.log(`Remaining: ${remaining}, resets at: ${resetAt.toISOString()}`);
burst_limit — Burst Limit
Possible causes:
- Sending more than 10 requests in a 1-second window
- Parallel test runners without request throttling
Resolution:
- Add a small delay (100ms) between sequential requests
- Use a request queue or semaphore to limit concurrency to 5–8 parallel requests
Execution Errors (500/502/504)
Returned when the capability execution fails due to infrastructure or runtime issues.
| Error Code | Status | Description |
|---|---|---|
| capability_timeout | 504 | The capability handler did not respond within the allowed timeout (default: 10 seconds). |
| runtime_unavailable | 502 | The provider's runtime endpoint is unreachable or returned a non-HTTP response. |
| execution_failed | 500 | The capability handler returned an unexpected error or the response did not match the declared schema. |
capability_timeout — Capability Timeout
Possible causes:
- Upstream API calls in your handler taking too long
- Database queries without proper timeouts or indexing
- Synchronous processing of large data sets
Resolution:
- Add timeouts to all external API calls within your handler (recommend 5s max)
- Implement caching for slow upstream data
- Consider returning partial data with a
degradedstatus rather than timing out completely - Use the trace store to identify which step in your handler is slow
runtime_unavailable — Runtime Unavailable
Possible causes:
- Your runtime server is down or not accepting connections
- DNS resolution failure for your runtime URL
- Firewall or security group blocking Aiffinity's IP range
- TLS certificate expired or misconfigured
Resolution:
- Verify your runtime is running and accessible from the public internet
- Check DNS records for your runtime domain
- Ensure your TLS certificate is valid and includes the correct domain
- Allow Aiffinity's egress IP range in your firewall: see Runtime Configuration
execution_failed — Execution Failed
Possible causes:
- Your handler threw an unhandled exception
- The response payload does not conform to the declared capability schema
- The handler returned a non-JSON response or incorrect Content-Type
Resolution:
- Wrap handler logic in try/catch and return structured error responses
- Validate your response against the schema before returning it
- Check the sandbox trace store for the full error stack trace
// Robust error handling in your handler
const weatherHandler: CapabilityHandler = {
async execute(request) {
try {
const weather = await fetchWeatherData(request.params.location);
return {
status: 'ok',
data: {
location: weather.city,
temperature_c: weather.temp,
condition: weather.condition,
},
ttl: 900,
};
} catch (error) {
// Return a structured error instead of letting exceptions propagate
if (error.code === 'UPSTREAM_TIMEOUT') {
return {
status: 'degraded',
error: {
type: 'upstream_timeout',
detail: 'Weather API did not respond within 5 seconds',
retryable: true,
},
};
}
return {
status: 'error',
error: {
type: 'internal_error',
detail: 'An unexpected error occurred',
retryable: false,
},
};
}
},
};
Certification Errors
These errors occur during the conformance testing and submission process, not during normal API operations.
| Error Code | Status | Description |
|---|---|---|
| conformance_failed | 400 | The package version did not pass the conformance suite. The aggregate score is below 80/100. |
| dimension_below_threshold | 400 | An individual conformance dimension scored critically low (below 5 points), indicating a fundamental issue that must be addressed regardless of the aggregate score. |
conformance_failed — Conformance Failed
Possible causes:
- Aggregate score below 80/100 across the 6 conformance dimensions
- Multiple minor issues across several dimensions adding up
Resolution:
- Run the conformance suite locally to see the full breakdown:
npx @aiffinity/provider-platform-sdk validate --local ./capability.json --full - Focus on the lowest-scoring dimensions first
- See the Certification Scores guide for details on each dimension
dimension_below_threshold — Dimension Below Threshold
Possible causes:
- A dimension scored below 5 points, indicating a fundamental gap (e.g., no error handling at all, completely missing documentation)
- The security dimension failed critically (e.g., credentials detected in responses)
Resolution:
- Check the
detailfield for the specific dimension and failing checks - Address the critical issues in that dimension before resubmitting
- A dimension score below 5 blocks submission even if the aggregate exceeds 80
Common Issues Troubleshooter
My package submission failed
Symptom: Calling client.packages.submit() returns a conformance_failed error even though local validation passed.
Cause: Local validation with --local (without --full) only checks 3 of 6 dimensions (schema, contracts, documentation). The submission process runs all 6 dimensions including performance, error handling, and security.
Fix: Run the full conformance suite against a running runtime: npx @aiffinity/provider-platform-sdk validate --local ./capability.json --runtime http://localhost:3000 --full
Webhook signatures don't match
Symptom: Webhook payloads arrive but your signature verification rejects them with a mismatch error.
Cause: The signature is computed over the raw request body. If your framework parses the body before you can access the raw bytes, the re-serialized JSON may differ (e.g., key ordering, whitespace).
Fix: Compute the HMAC-SHA256 signature over the raw request body bytes, not a parsed-and-re-serialized version. In Express, use express.raw({ type: 'application/json' }) on the webhook route. In Fastify, use request.rawBody.
Capability timeout in testing
Symptom: The conformance suite reports capability_timeout during performance testing, but your handler responds quickly locally.
Cause: The conformance suite adds realistic network latency. If your handler takes 8–9 seconds, the additional network round-trip pushes it past the 10-second timeout.
Fix: Aim for handler response times under 5 seconds to leave headroom for network latency. Use the trace store to identify slow steps. Add caching for upstream API calls.
Schema validation passes locally but fails remotely
Symptom: validate --local passes but the remote conformance run reports invalid_schema.
Cause: Your local SDK version may be outdated and using a different schema validation library version. The remote suite always uses the latest validation rules.
Fix: Update to the latest SDK: npm install @aiffinity/provider-platform-sdk@latest. Then re-run local validation to confirm alignment.
Rate limited during CI builds
Symptom: CI pipelines intermittently fail with rate_limit_exceeded errors.
Cause: Multiple CI runs sharing the same client_id compete for the same rate limit quota.
Fix: Create a dedicated sandbox app for CI with its own credentials. Add request throttling in your CI scripts. Use the --min-score flag to fail fast without running the full suite on every commit.
Retry Guidance
Not all errors should be retried. Use this table to determine whether to retry, fix, or escalate.
| Error Code | Retryable | Strategy |
|---|---|---|
| invalid_schema | Terminal | Fix the schema and resubmit. Retrying will produce the same error. |
| missing_field | Terminal | Include the missing field in the request. Retrying without changes will fail. |
| invalid_format | Terminal | Correct the field format and resubmit. |
| token_expired | Retryable | Refresh the token, then retry the original request immediately. |
| insufficient_scope | Terminal | Update app scopes and obtain a new token. Retrying with the same token will fail. |
| account_suspended | Terminal | Address the suspension via the developer console. No automated retry. |
| rate_limit_exceeded | Retryable | Wait for Retry-After seconds, then retry with exponential backoff and jitter. |
| burst_limit | Retryable | Wait 1–2 seconds, then retry. Reduce request concurrency. |
| capability_timeout | Retryable | Retry once after 2–5 seconds. If it fails again, investigate the handler's performance. |
| runtime_unavailable | Retryable | Retry with exponential backoff (max 3 attempts). If persistent, check runtime health. |
| execution_failed | Terminal | Investigate the handler error via trace store. Fix the root cause before retrying. |
| conformance_failed | Terminal | Improve your package to meet the 80/100 threshold, then resubmit. |
| dimension_below_threshold | Terminal | Address the critical dimension gap, then resubmit. |
Recommended backoff implementation
async function retryWithBackoff<T>(
fn: () => Promise<T>,
maxRetries = 3,
baseDelay = 1000,
): Promise<T> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error: any) {
// Don't retry terminal errors
const terminalTypes = [
'invalid_schema', 'missing_field', 'invalid_format',
'insufficient_scope', 'account_suspended',
'execution_failed', 'conformance_failed',
'dimension_below_threshold',
];
if (terminalTypes.includes(error.type) || attempt === maxRetries) {
throw error;
}
// Respect Retry-After header if present
const retryAfter = error.headers?.['retry-after'];
const delay = retryAfter
? parseInt(retryAfter, 10) * 1000
: baseDelay * Math.pow(2, attempt) + Math.random() * 1000;
console.log(`Attempt ${attempt + 1} failed, retrying in ${Math.round(delay)}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw new Error('Unreachable');
}