API Errors
This guide covers error handling for the Teabar API, including error codes, response formats, and best practices.
Error Response Format
All API errors follow a consistent format:
{
"code": "not_found",
"message": "Environment 'env_abc123' not found",
"details": [
{
"@type": "type.googleapis.com/teabar.v1.ErrorInfo",
"reason": "ENVIRONMENT_NOT_FOUND",
"domain": "teabar.dev",
"metadata": {
"environmentId": "env_abc123"
}
}
]
} Error Codes
Standard Codes
| Code | HTTP Status | Description |
|---|---|---|
ok | 200 | Success (not an error) |
cancelled | 499 | Request cancelled by client |
unknown | 500 | Unknown error |
invalid_argument | 400 | Invalid request parameters |
deadline_exceeded | 504 | Request timed out |
not_found | 404 | Resource doesn’t exist |
already_exists | 409 | Resource already exists |
permission_denied | 403 | Insufficient permissions |
resource_exhausted | 429 | Rate limit or quota exceeded |
failed_precondition | 400 | Operation not allowed in current state |
aborted | 409 | Operation aborted (conflict) |
out_of_range | 400 | Value out of valid range |
unimplemented | 501 | Feature not implemented |
internal | 500 | Internal server error |
unavailable | 503 | Service unavailable |
data_loss | 500 | Unrecoverable data loss |
unauthenticated | 401 | Authentication required |
Domain-Specific Reasons
Error details include domain-specific reason codes:
Authentication Errors:
| Reason | Description |
|---|---|
TOKEN_EXPIRED | Authentication token has expired |
TOKEN_INVALID | Token format or signature invalid |
TOKEN_REVOKED | Token has been revoked |
INSUFFICIENT_SCOPE | Token lacks required scope |
Environment Errors:
| Reason | Description |
|---|---|
ENVIRONMENT_NOT_FOUND | Environment doesn’t exist |
ENVIRONMENT_NOT_RUNNING | Environment is not in running state |
ENVIRONMENT_ALREADY_RUNNING | Environment is already running |
ENVIRONMENT_PROVISIONING_FAILED | Provisioning error |
ENVIRONMENT_QUOTA_EXCEEDED | Too many environments |
Blueprint Errors:
| Reason | Description |
|---|---|
BLUEPRINT_NOT_FOUND | Blueprint doesn’t exist |
BLUEPRINT_INVALID | Blueprint validation failed |
BLUEPRINT_IN_USE | Blueprint used by environments |
Organization Errors:
| Reason | Description |
|---|---|
ORGANIZATION_NOT_FOUND | Organization doesn’t exist |
PROJECT_NOT_FOUND | Project doesn’t exist |
MEMBER_NOT_FOUND | Member doesn’t exist |
INVITATION_EXPIRED | Invitation has expired |
Error Details
ErrorInfo
Standard error information:
{
"@type": "type.googleapis.com/teabar.v1.ErrorInfo",
"reason": "ENVIRONMENT_QUOTA_EXCEEDED",
"domain": "teabar.dev",
"metadata": {
"limit": "10",
"current": "10"
}
} BadRequest
Field-level validation errors:
{
"@type": "type.googleapis.com/google.rpc.BadRequest",
"fieldViolations": [
{
"field": "name",
"description": "Name must be lowercase alphanumeric"
},
{
"field": "ttl",
"description": "TTL must be between 1h and 30d"
}
]
} RetryInfo
When to retry failed requests:
{
"@type": "type.googleapis.com/google.rpc.RetryInfo",
"retryDelay": "30s"
} QuotaFailure
Quota violation details:
{
"@type": "type.googleapis.com/google.rpc.QuotaFailure",
"violations": [
{
"subject": "environments",
"description": "Environment quota exceeded: 10/10"
}
]
} Handling Errors
TypeScript/JavaScript
import { ConnectError, Code } from "@connectrpc/connect";
try {
await client.createEnvironment(request);
} catch (err) {
if (err instanceof ConnectError) {
switch (err.code) {
case Code.NotFound:
console.error("Resource not found:", err.message);
break;
case Code.PermissionDenied:
console.error("Permission denied:", err.message);
break;
case Code.ResourceExhausted:
// Extract retry delay if available
const retryInfo = err.findDetails(RetryInfo);
if (retryInfo.length > 0) {
const delay = retryInfo[0].retryDelay;
console.log(`Rate limited. Retry after ${delay}`);
}
break;
default:
console.error("API error:", err.message);
}
}
} Go
import (
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
resp, err := client.CreateEnvironment(ctx, req)
if err != nil {
st, ok := status.FromError(err)
if !ok {
return fmt.Errorf("unknown error: %w", err)
}
switch st.Code() {
case codes.NotFound:
return fmt.Errorf("not found: %s", st.Message())
case codes.PermissionDenied:
return fmt.Errorf("permission denied: %s", st.Message())
case codes.ResourceExhausted:
// Check for retry info in details
for _, detail := range st.Details() {
if ri, ok := detail.(*errdetails.RetryInfo); ok {
delay := ri.GetRetryDelay().AsDuration()
time.Sleep(delay)
return retry(ctx, req)
}
}
default:
return fmt.Errorf("API error: %s", st.Message())
}
} Python
from grpc import StatusCode
try:
response = client.CreateEnvironment(request)
except grpc.RpcError as e:
code = e.code()
if code == StatusCode.NOT_FOUND:
print(f"Not found: {e.details()}")
elif code == StatusCode.PERMISSION_DENIED:
print(f"Permission denied: {e.details()}")
elif code == StatusCode.RESOURCE_EXHAUSTED:
print(f"Rate limited: {e.details()}")
else:
print(f"Error: {e.details()}") Retry Strategies
Retryable Errors
These errors are typically safe to retry:
| Code | Retry? | Notes |
|---|---|---|
unavailable | Yes | Service temporarily down |
resource_exhausted | Yes | After delay from RetryInfo |
deadline_exceeded | Maybe | Depends on idempotency |
aborted | Yes | Transaction conflict |
internal | Maybe | Depends on operation |
Non-Retryable Errors
| Code | Retry? | Notes |
|---|---|---|
invalid_argument | No | Fix request first |
not_found | No | Resource doesn’t exist |
already_exists | No | Resource exists |
permission_denied | No | Check permissions |
unauthenticated | No | Re-authenticate first |
Exponential Backoff
async function withRetry<T>(
operation: () => Promise<T>,
maxRetries: number = 3
): Promise<T> {
let lastError: Error;
for (let i = 0; i < maxRetries; i++) {
try {
return await operation();
} catch (err) {
if (err instanceof ConnectError) {
if (!isRetryable(err.code)) {
throw err;
}
lastError = err;
const delay = Math.pow(2, i) * 1000 + Math.random() * 1000;
await sleep(delay);
} else {
throw err;
}
}
}
throw lastError!;
} Common Error Scenarios
Authentication Errors
{
"code": "unauthenticated",
"message": "Invalid or expired token",
"details": [{
"reason": "TOKEN_EXPIRED",
"metadata": {
"expiredAt": "2024-03-10T00:00:00Z"
}
}]
} Resolution: Refresh token or re-authenticate.
Validation Errors
{
"code": "invalid_argument",
"message": "Invalid request",
"details": [{
"@type": "type.googleapis.com/google.rpc.BadRequest",
"fieldViolations": [
{"field": "name", "description": "Required field"}
]
}]
} Resolution: Fix request parameters per field violations.
Rate Limiting
{
"code": "resource_exhausted",
"message": "Rate limit exceeded",
"details": [{
"@type": "type.googleapis.com/google.rpc.RetryInfo",
"retryDelay": "30s"
}]
} Resolution: Wait for specified delay, then retry.
See Also
- API Authentication - Auth errors
- Rate Limits - Rate limit details