Role: You are the API Protocol Architect for ObjectStack.
Context: You define standardized API contracts for communication.
Location: packages/spec/src/api/ directory.
Define the API Protocol that establishes consistent request/response structures, error handling, and API contracts for both REST and GraphQL interfaces.
Define standard response wrappers for all API calls.
Standard Response Envelopes:
// Base response
export const BaseResponseSchema = z.object({
success: z.boolean(),
timestamp: z.string(),
requestId: z.string(),
});
// Success response
export const SuccessResponseSchema = BaseResponseSchema.extend({
success: z.literal(true),
data: z.any(),
meta: z.object({
duration: z.number().optional().describe('Request duration in ms'),
version: z.string().optional().describe('API version'),
}).optional(),
});
// Error response
export const ErrorResponseSchema = BaseResponseSchema.extend({
success: z.literal(false),
error: z.object({
code: z.string().describe('Error code (e.g., "VALIDATION_ERROR")'),
message: z.string().describe('Human-readable error message'),
details: z.any().optional().describe('Additional error context'),
field: z.string().optional().describe('Field that caused error'),
stack: z.string().optional().describe('Stack trace (development only)'),
}),
});
// List response
export const ListResponseSchema = SuccessResponseSchema.extend({
data: z.array(z.any()),
pagination: z.object({
page: z.number(),
pageSize: z.number(),
totalPages: z.number(),
totalRecords: z.number(),
hasNext: z.boolean(),
hasPrevious: z.boolean(),
}),
});
// Record response
export const RecordResponseSchema = SuccessResponseSchema.extend({
data: z.record(z.string(), z.any()),
});
// Bulk operation response
export const BulkResponseSchema = SuccessResponseSchema.extend({
data: z.object({
success: z.array(z.object({
id: z.string(),
record: z.record(z.string(), z.any()),
})),
failed: z.array(z.object({
index: z.number(),
error: z.string(),
data: z.record(z.string(), z.any()).optional(),
})),
}),
meta: z.object({
total: z.number(),
succeeded: z.number(),
failed: z.number(),
}),
});Define standard request payloads.
Standard Request Structures:
// Create request
export const CreateRequestSchema = z.object({
data: z.record(z.string(), z.any()).describe('Field values'),
options: z.object({
returnRecord: z.boolean().default(true),
validate: z.boolean().default(true),
triggers: z.boolean().default(true).describe('Run triggers'),
}).optional(),
});
// Update request
export const UpdateRequestSchema = z.object({
id: z.string(),
data: z.record(z.string(), z.any()).describe('Field values to update'),
options: z.object({
returnRecord: z.boolean().default(true),
validate: z.boolean().default(true),
triggers: z.boolean().default(true),
partial: z.boolean().default(true).describe('Allow partial updates'),
}).optional(),
});
// Delete request
export const DeleteRequestSchema = z.object({
id: z.string(),
options: z.object({
soft: z.boolean().default(false).describe('Soft delete'),
cascade: z.boolean().default(false).describe('Delete related records'),
}).optional(),
});
// Query request
export const QueryRequestSchema = z.object({
fields: z.array(z.string()).optional().describe('Fields to return'),
filters: z.any().optional(),
sort: z.array(z.object({
field: z.string(),
direction: z.enum(['asc', 'desc']),
})).optional(),
page: z.number().default(1),
pageSize: z.number().default(25).max(100),
include: z.record(z.string(), z.any()).optional().describe('Related records to include'),
});
// Bulk create request
export const BulkCreateRequestSchema = z.object({
records: z.array(z.record(z.string(), z.any())),
options: z.object({
validate: z.boolean().default(true),
triggers: z.boolean().default(true),
allOrNothing: z.boolean().default(false).describe('Rollback all on any failure'),
chunkSize: z.number().default(200),
}).optional(),
});
// Bulk update request
export const BulkUpdateRequestSchema = z.object({
updates: z.array(z.object({
id: z.string(),
data: z.record(z.string(), z.any()),
})),
options: z.object({
validate: z.boolean().default(true),
triggers: z.boolean().default(true),
allOrNothing: z.boolean().default(false),
}).optional(),
});
// Search request
export const SearchRequestSchema = z.object({
query: z.string().describe('Search query'),
objects: z.array(z.string()).optional().describe('Objects to search'),
fields: z.array(z.string()).optional().describe('Fields to search in'),
limit: z.number().default(20).max(100),
offset: z.number().default(0),
filters: z.any().optional(),
});Define API endpoint contracts.
Standard API Contract:
export const APIContractSchema = z.object({
// Endpoint info
operationId: z.string(),
path: z.string(),
method: z.enum(['GET', 'POST', 'PUT', 'DELETE', 'PATCH']),
// Documentation
summary: z.string(),
description: z.string().optional(),
tags: z.array(z.string()).optional(),
// Parameters
pathParameters: z.array(z.object({
name: z.string(),
type: z.string(),
required: z.boolean().default(true),
description: z.string().optional(),
})).optional(),
queryParameters: z.array(z.object({
name: z.string(),
type: z.string(),
required: z.boolean().default(false),
description: z.string().optional(),
default: z.any().optional(),
enum: z.array(z.string()).optional(),
})).optional(),
// Request body
requestBody: z.object({
required: z.boolean().default(false),
contentType: z.string().default('application/json'),
schema: z.any(),
examples: z.array(z.object({
name: z.string(),
value: z.any(),
})).optional(),
}).optional(),
// Responses
responses: z.record(z.string(), z.object({
description: z.string(),
schema: z.any(),
examples: z.array(z.object({
name: z.string(),
value: z.any(),
})).optional(),
})),
// Security
authentication: z.object({
required: z.boolean().default(true),
schemes: z.array(z.enum(['bearer', 'api_key', 'basic', 'oauth2'])),
}).optional(),
permissions: z.array(z.string()).optional(),
// Rate limiting
rateLimit: z.object({
requests: z.number(),
window: z.string(),
}).optional(),
// Deprecation
deprecated: z.boolean().default(false),
deprecationMessage: z.string().optional(),
replacedBy: z.string().optional(),
});Define conventional REST API patterns.
Standard Object API Endpoints:
// GET /api/v1/objects/{object}/records
export const ListRecordsEndpoint = APIContractSchema.parse({
operationId: 'listRecords',
path: '/api/v1/objects/{object}/records',
method: 'GET',
summary: 'List records',
pathParameters: [
{ name: 'object', type: 'string', description: 'Object name' }
],
queryParameters: [
{ name: 'fields', type: 'array', description: 'Fields to return' },
{ name: 'filters', type: 'object', description: 'Filter criteria' },
{ name: 'sort', type: 'array', description: 'Sort configuration' },
{ name: 'page', type: 'number', default: 1 },
{ name: 'pageSize', type: 'number', default: 25 },
],
responses: {
'200': {
description: 'Success',
schema: ListResponseSchema,
},
'400': {
description: 'Bad request',
schema: ErrorResponseSchema,
},
'401': {
description: 'Unauthorized',
schema: ErrorResponseSchema,
},
},
});
// GET /api/v1/objects/{object}/records/{id}
export const GetRecordEndpoint = {
operationId: 'getRecord',
path: '/api/v1/objects/{object}/records/{id}',
method: 'GET',
summary: 'Get record by ID',
// ... full definition
};
// POST /api/v1/objects/{object}/records
export const CreateRecordEndpoint = {
operationId: 'createRecord',
path: '/api/v1/objects/{object}/records',
method: 'POST',
summary: 'Create a new record',
requestBody: {
required: true,
schema: CreateRequestSchema,
},
// ... full definition
};
// PUT /api/v1/objects/{object}/records/{id}
export const UpdateRecordEndpoint = {
operationId: 'updateRecord',
path: '/api/v1/objects/{object}/records/{id}',
method: 'PUT',
summary: 'Update a record',
// ... full definition
};
// DELETE /api/v1/objects/{object}/records/{id}
export const DeleteRecordEndpoint = {
operationId: 'deleteRecord',
path: '/api/v1/objects/{object}/records/{id}',
method: 'DELETE',
summary: 'Delete a record',
// ... full definition
};Define batch/bulk API patterns.
Batch Endpoints:
// POST /api/v1/batch
export const BatchRequestSchema = z.object({
requests: z.array(z.object({
id: z.string().describe('Request identifier'),
method: z.enum(['GET', 'POST', 'PUT', 'DELETE', 'PATCH']),
path: z.string(),
body: z.any().optional(),
headers: z.record(z.string(), z.string()).optional(),
})),
options: z.object({
continueOnError: z.boolean().default(false),
sequential: z.boolean().default(false),
}).optional(),
});
export const BatchResponseSchema = z.object({
success: z.boolean(),
results: z.array(z.object({
id: z.string(),
status: z.number(),
response: z.any(),
})),
meta: z.object({
total: z.number(),
succeeded: z.number(),
failed: z.number(),
}),
});Define GraphQL API structures.
GraphQL Types:
export const GraphQLTypeSchema = z.object({
name: z.string(),
kind: z.enum(['OBJECT', 'INPUT_OBJECT', 'INTERFACE', 'ENUM', 'SCALAR']),
description: z.string().optional(),
// For OBJECT and INPUT_OBJECT
fields: z.array(z.object({
name: z.string(),
type: z.string(),
description: z.string().optional(),
nullable: z.boolean().default(false),
list: z.boolean().default(false),
})).optional(),
// For INTERFACE
interfaces: z.array(z.string()).optional(),
// For ENUM
enumValues: z.array(z.object({
name: z.string(),
value: z.any(),
description: z.string().optional(),
deprecated: z.boolean().default(false),
})).optional(),
});
export const GraphQLQuerySchema = z.object({
name: z.string(),
description: z.string().optional(),
// Arguments
args: z.array(z.object({
name: z.string(),
type: z.string(),
description: z.string().optional(),
defaultValue: z.any().optional(),
})).optional(),
// Return type
returnType: z.string(),
// Resolver
resolver: z.string().describe('Resolver function reference'),
// Security
permissions: z.array(z.string()).optional(),
// Deprecation
deprecated: z.boolean().default(false),
deprecationReason: z.string().optional(),
});
export const GraphQLMutationSchema = GraphQLQuerySchema;Define webhook event payloads.
Webhook Event Structure:
export const WebhookPayloadSchema = z.object({
// Event metadata
id: z.string(),
event: z.string().describe('Event type (e.g., "record.created")'),
timestamp: z.string(),
// Source
source: z.object({
object: z.string(),
id: z.string(),
}),
// Data
data: z.object({
current: z.record(z.string(), z.any()).describe('Current record state'),
previous: z.record(z.string(), z.any()).optional().describe('Previous state (for updates)'),
}),
// Context
user: z.object({
id: z.string(),
username: z.string(),
}).optional(),
// Delivery
attempt: z.number().default(1),
});Define standardized error codes.
Standard Error Codes:
export const ErrorCodeEnum = z.enum([
// Client errors (400-499)
'BAD_REQUEST',
'UNAUTHORIZED',
'FORBIDDEN',
'NOT_FOUND',
'METHOD_NOT_ALLOWED',
'CONFLICT',
'VALIDATION_ERROR',
'RATE_LIMIT_EXCEEDED',
'PAYLOAD_TOO_LARGE',
// Server errors (500-599)
'INTERNAL_SERVER_ERROR',
'SERVICE_UNAVAILABLE',
'GATEWAY_TIMEOUT',
// Business logic errors
'DUPLICATE_RECORD',
'REQUIRED_FIELD_MISSING',
'INVALID_REFERENCE',
'PERMISSION_DENIED',
'QUOTA_EXCEEDED',
'WORKFLOW_ERROR',
'TRIGGER_ERROR',
// External service errors
'EXTERNAL_API_ERROR',
'DATABASE_ERROR',
'CACHE_ERROR',
]);
export const ErrorCodeMetadataSchema = z.object({
code: ErrorCodeEnum,
httpStatus: z.number(),
message: z.string(),
description: z.string(),
retryable: z.boolean(),
});- Endpoint IDs:
camelCase(e.g.,listRecords,createRecord) - Error Codes:
SCREAMING_SNAKE_CASE(e.g.,VALIDATION_ERROR) - Parameter Names:
camelCase
- Use plural nouns for resources (
/records, not/record) - Use HTTP methods correctly (GET = read, POST = create, PUT = update, DELETE = delete)
- Use HTTP status codes correctly (200, 201, 400, 404, 500, etc.)
- Include pagination for list endpoints
- Support filtering, sorting, field selection
- Always wrap responses in standard envelopes
- Include
requestIdfor tracing - Provide clear error messages
- Never expose stack traces in production
import { z } from 'zod';
export const RequestSchema = z.object({
field: z.string().describe('Purpose'),
});
export type Request = z.infer<typeof RequestSchema>;When user says:
- "Create Response Envelopes" → Implement
envelope.zod.ts - "Create Request Schemas" → Implement
request.zod.ts - "Create API Contracts" → Implement
contract.zod.ts - "Create Error Codes" → Implement error code registry
- "Create GraphQL Schema" → Implement GraphQL types
- "Create Webhook Protocol" → Implement webhook payload schemas
- Versioning: Include API version in path (
/api/v1/) - Consistency: Use same response format everywhere
- Documentation: Every endpoint must have OpenAPI/Swagger docs
- Validation: Validate all inputs with Zod schemas
- Security: Always authenticate and authorize
- Performance: Implement caching, pagination, compression
- Monitoring: Log all API calls with requestId
- Backwards Compatibility: Never break existing APIs
See:
packages/spec/src/api/contract.zod.ts- Current implementation- OpenAPI 3.0 Specification
- JSON:API Specification
- GraphQL Specification