This guide provides step-by-step instructions for implementing the optimized Custom GPT Actions API for ChittyConnect. The optimizations focus on making the API more intuitive, reliable, and efficient for AI assistants (ChatGPT, Claude) while maintaining backward compatibility.
- Rich descriptions with context and use cases
- Comprehensive examples for every endpoint
- x-gpt-hints metadata for improved GPT understanding
- Detailed error responses with recovery guidance
- All-in-one operations reduce round trips
- Atomic transactions with rollback support
- Contextual responses guide next steps
- Batch processing for multiple operations
- Structured error codes for consistent handling
- Recovery suggestions in every error
- Development vs production error detail levels
- Partial success handling for batch operations
- Conversation tracking across requests
- State management via KV storage
- Continuation hints for multi-turn interactions
- Suggested next steps in responses
- Response caching for read operations
- Request batching support
- Streaming responses for long operations
- Parallel processing where applicable
chittyconnect/
├── public/
│ ├── openapi.json # Current OpenAPI spec
│ └── openapi-optimized.json # New optimized spec
├── src/
│ ├── api/
│ │ ├── router.js # Main API router
│ │ ├── middleware/
│ │ │ ├── auth.js # Authentication
│ │ │ ├── context.js # Context injection (NEW)
│ │ │ ├── validation.js # Request validation (NEW)
│ │ │ └── cache.js # Response caching (NEW)
│ │ └── routes/
│ │ ├── composite.js # Composite endpoints (NEW)
│ │ ├── chittyid.js
│ │ ├── chittycases.js
│ │ └── ...
│ └── lib/
│ ├── responses.js # Response utilities (NEW)
│ ├── errors.js # Error classes (NEW)
│ └── credential-helper.js # 1Password integration
└── docs/
├── GPT_ACTIONS_OPTIMIZATION_RECOMMENDATIONS.md
└── GPT_ACTIONS_IMPLEMENTATION_GUIDE.md (this file)
cd /Users/nb/Projects/development/chittyconnect
npm install zod@latest # Validation libraryAdd new KV namespaces for context storage:
[[kv_namespaces]]
binding = "CONVERSATIONS"
id = "your_conversations_kv_namespace_id"
preview_id = "your_preview_id"
[[kv_namespaces]]
binding = "CACHE"
id = "your_cache_kv_namespace_id"
preview_id = "your_preview_id"
[[kv_namespaces]]
binding = "CONTEXT_STORE"
id = "your_context_kv_namespace_id"
preview_id = "your_preview_id"Create the namespaces:
wrangler kv:namespace create "CONVERSATIONS"
wrangler kv:namespace create "CONVERSATIONS" --preview
wrangler kv:namespace create "CACHE"
wrangler kv:namespace create "CACHE" --preview
wrangler kv:namespace create "CONTEXT_STORE"
wrangler kv:namespace create "CONTEXT_STORE" --previewCreate /Users/nb/Projects/development/chittyconnect/src/api/middleware/context.js:
/**
* Context Injection Middleware
* Automatically injects contextual information into requests
*/
export async function injectContext(c, next) {
const startTime = Date.now();
c.set('startTime', startTime);
const auth = c.get('auth');
const conversationId = c.req.header('X-Conversation-ID');
// Build context object
const context = {
user: {
id: auth?.userId,
scopes: auth?.scopes
},
session: {
conversationId: conversationId || crypto.randomUUID(),
requestId: crypto.randomUUID(),
timestamp: new Date().toISOString()
},
environment: {
source: detectSource(c.req.header('User-Agent')),
clientIp: c.req.header('CF-Connecting-IP'),
country: c.req.header('CF-IPCountry')
}
};
// Retrieve historical context if available
if (conversationId) {
try {
const history = await c.env.CONTEXT_STORE.get(
`context:${conversationId}`,
{ type: 'json' }
);
if (history) {
context.history = history;
}
} catch (error) {
console.warn('Failed to retrieve context history:', error);
}
}
// Inject context into request
c.set('context', context);
// Process request
await next();
// Store updated context if conversation ID is present
if (conversationId && c.res.status < 400) {
try {
const responseBody = await c.res.clone().json();
// Update context with response information
context.lastRequest = {
path: c.req.path,
method: c.req.method,
timestamp: new Date().toISOString(),
responseStatus: c.res.status
};
if (responseBody.data) {
context.lastResult = {
type: extractResultType(c.req.path),
id: extractEntityId(responseBody.data)
};
}
await c.env.CONTEXT_STORE.put(
`context:${conversationId}`,
JSON.stringify(context),
{ expirationTtl: 86400 } // 24 hours
);
} catch (error) {
console.warn('Failed to store context:', error);
}
}
}
function detectSource(userAgent) {
if (!userAgent) return 'unknown';
if (userAgent.includes('OpenAI')) return 'openai-gpt';
if (userAgent.includes('Anthropic')) return 'anthropic-claude';
return 'generic';
}
function extractResultType(path) {
if (path.includes('/chittyid/')) return 'chittyid';
if (path.includes('/cases/')) return 'case';
if (path.includes('/evidence/')) return 'evidence';
return 'unknown';
}
function extractEntityId(data) {
return data?.chittyid || data?.id || data?.caseId || null;
}Create /Users/nb/Projects/development/chittyconnect/src/api/middleware/cache.js:
/**
* Response Caching Middleware
* Caches GET request responses
*/
export function cacheResponse(ttl = 300) {
return async (c, next) => {
// Only cache GET requests
if (c.req.method !== 'GET') {
return next();
}
const cacheKey = `cache:${c.req.url}`;
// Check cache
try {
const cached = await c.env.CACHE.get(cacheKey, { type: 'json' });
if (cached) {
c.header('X-Cache-Status', 'HIT');
c.header('X-Cache-Key', cacheKey);
return c.json(cached);
}
} catch (error) {
console.warn('Cache read error:', error);
}
// Process request
await next();
// Cache successful responses
if (c.res.status === 200) {
try {
const response = await c.res.clone().json();
await c.env.CACHE.put(
cacheKey,
JSON.stringify(response),
{ expirationTtl: ttl }
);
c.header('X-Cache-Status', 'MISS');
} catch (error) {
console.warn('Cache write error:', error);
}
}
};
}
export function invalidateCache(c, pattern) {
// Implementation for cache invalidation
// This would use KV list() to find matching keys and delete them
}Update /Users/nb/Projects/development/chittyconnect/src/api/router.js:
import { Hono } from "hono";
import { cors } from "hono/cors";
import { logger } from "hono/logger";
// Import routes
import { chittyidRoutes } from "./routes/chittyid.js";
import { chittycasesRoutes } from "./routes/chittycases.js";
import { compositeRoutes } from "./routes/composite.js"; // NEW
// Import middleware
import { authenticate } from "./middleware/auth.js";
import { injectContext } from "./middleware/context.js"; // NEW
import { errorHandler } from "../lib/errors.js"; // NEW
const api = new Hono();
// Global middleware
api.use("*", logger());
api.use("*", cors({
origin: ["https://chat.openai.com", "https://chatgpt.com", "https://claude.ai"],
allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"],
allowHeaders: [
"Content-Type",
"Authorization",
"X-ChittyOS-API-Key",
"X-Conversation-ID",
"X-Request-ID"
],
exposeHeaders: [
"Content-Length",
"X-Request-ID",
"X-Cache-Status",
"X-Conversation-ID"
],
maxAge: 86400,
credentials: true,
}));
// Health check (no auth required)
api.get("/api/health", (c) => {
return c.json({
status: "healthy",
service: "chittyconnect-gpt-api",
version: "2.0.0",
timestamp: new Date().toISOString()
});
});
// OpenAPI spec endpoints
api.get("/openapi.json", async (c) => {
const spec = await c.env.ASSETS.fetch(
new Request("https://connect.chitty.cc/openapi.json")
);
return spec;
});
api.get("/openapi-v2.json", async (c) => {
const spec = await c.env.ASSETS.fetch(
new Request("https://connect.chitty.cc/openapi-optimized.json")
);
return spec;
});
// Authentication and context for all API routes
api.use("/api/*", authenticate);
api.use("/api/*", injectContext);
// Route handlers
api.route("/api/composite", compositeRoutes); // NEW - Composite endpoints
api.route("/api/chittyid", chittyidRoutes);
api.route("/api/chittycases", chittycasesRoutes);
// ... other routes
// Global error handler
api.onError(errorHandler);
export { api };The composite routes have already been created in /Users/nb/Projects/development/chittyconnect/src/api/routes/composite.js. No additional work needed.
The error utilities have been created in /Users/nb/Projects/development/chittyconnect/src/lib/errors.js and /Users/nb/Projects/development/chittyconnect/src/lib/responses.js.
Update existing routes to use the new error handling:
// Example: Update chittyid.js
import { errorResponse } from '../../lib/responses.js';
import { APIError, ServiceUnavailableError } from '../../lib/errors.js';
chittyidRoutes.post("/mint", async (c) => {
try {
const { entity, metadata } = await c.req.json();
if (!entity) {
throw new APIError('VALIDATION_ERROR', 'entity is required', null, 400);
}
// ... rest of logic
} catch (error) {
if (error instanceof APIError) {
return errorResponse(c, error);
}
return errorResponse(c, new ServiceUnavailableError('chittyid', error.message));
}
});Create /Users/nb/Projects/development/chittyconnect/tests/api/composite.test.js:
import { describe, it, expect, beforeAll } from 'vitest';
import { api } from '../../src/api/router.js';
describe('Composite API Endpoints', () => {
let env;
beforeAll(() => {
env = {
CONVERSATIONS: mockKVNamespace(),
CACHE: mockKVNamespace(),
CONTEXT_STORE: mockKVNamespace(),
// ... other bindings
};
});
it('should create complete case with parties', async () => {
const request = new Request('https://connect.chitty.cc/api/composite/case-with-parties', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer test-token',
'X-Conversation-ID': 'test-conv-123'
},
body: JSON.stringify({
caseDetails: {
title: 'Test Case',
type: 'eviction'
},
parties: [
{
role: 'plaintiff',
name: 'John Doe',
type: 'individual'
}
]
})
});
const response = await api.fetch(request, env);
const data = await response.json();
expect(response.status).toBe(200);
expect(data.success).toBe(true);
expect(data.data.case).toBeDefined();
expect(data.data.parties).toHaveLength(1);
expect(data.context.conversationId).toBe('test-conv-123');
});
it('should handle batch operations', async () => {
const request = new Request('https://connect.chitty.cc/api/composite/batch', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer test-token'
},
body: JSON.stringify({
requests: [
{
id: 'req1',
method: 'POST',
endpoint: '/api/chittyid/mint',
body: { entity: 'PEO' }
},
{
id: 'req2',
method: 'POST',
endpoint: '/api/chittyid/mint',
body: { entity: 'PLACE' }
}
],
sequential: false
})
});
const response = await api.fetch(request, env);
const data = await response.json();
expect(response.status).toBe(200);
expect(data.summary.total).toBe(2);
expect(data.results).toHaveLength(2);
});
});
function mockKVNamespace() {
const store = new Map();
return {
get: async (key) => store.get(key),
put: async (key, value) => store.set(key, value),
delete: async (key) => store.delete(key)
};
}npm testUpdate CLAUDE.md and README.md with new endpoints and features.
# Deploy optimized OpenAPI spec
wrangler publish --env staging
# Test with staging URL
curl https://connect-staging.chitty.cc/openapi-v2.json- Go to ChatGPT → Create GPT
- Configure Actions:
- Schema: Import from
https://connect.chitty.cc/openapi-v2.json - Authentication: API Key (Custom header
X-ChittyOS-API-Key)
- Schema: Import from
- Test the GPT with various prompts
wrangler publish --env productionUser Prompt to GPT:
Create an eviction case for John Smith (landlord) vs Jane Johnson (tenant).
The lease was signed on Jan 15, 2023 and there are 3 months of missed payments
totaling $4,500.
GPT Action Call:
POST /api/composite/case-with-parties
Content-Type: application/json
X-Conversation-ID: conv_abc123
{
"caseDetails": {
"title": "Smith vs. Johnson - Eviction Proceeding",
"type": "eviction",
"description": "Non-payment of rent for 3 months"
},
"parties": [
{
"role": "plaintiff",
"name": "John Smith",
"type": "individual"
},
{
"role": "defendant",
"name": "Jane Johnson",
"type": "individual"
}
],
"initialEvidence": {
"leaseAgreement": {
"type": "document",
"description": "Original lease agreement signed 2023-01-15"
},
"paymentHistory": {
"type": "financial",
"description": "Payment records showing missed payments",
"data": {
"missedPayments": 3,
"totalOwed": 4500
}
}
}
}Response:
{
"success": true,
"data": {
"case": {
"id": "01-C-CTX-A7B2-C-2411-3-X",
"title": "Smith vs. Johnson - Eviction Proceeding",
"status": "active"
},
"parties": [
{
"chittyId": "01-C-PEO-A7B3-P-2411-4-X",
"role": "plaintiff",
"name": "John Smith"
},
{
"chittyId": "01-C-PEO-A7B4-P-2411-5-X",
"role": "defendant",
"name": "Jane Johnson"
}
],
"evidence": [
{
"id": "01-C-INFO-A7B5-I-2411-6-X",
"type": "document",
"status": "verified"
}
]
},
"context": {
"conversationId": "conv_abc123",
"continuationHint": "Case created successfully. You can now add more evidence, schedule hearings, or generate legal documents.",
"suggestedNextSteps": [
{
"action": "Generate eviction notice",
"endpoint": "/api/documents/generate",
"reason": "Create formal eviction notice"
}
]
}
}Request:
POST /api/composite/batch
Content-Type: application/json
{
"requests": [
{
"id": "create_plaintiff",
"method": "POST",
"endpoint": "/api/chittyid/mint",
"body": { "entity": "PEO", "metadata": { "name": "John Doe" } }
},
{
"id": "create_defendant",
"method": "POST",
"endpoint": "/api/chittyid/mint",
"body": { "entity": "PEO", "metadata": { "name": "Jane Doe" } }
}
],
"sequential": false
}-
API Performance
- Response times per endpoint
- Cache hit rates
- Error rates by error code
-
GPT Usage Patterns
- Most used endpoints
- Conversation lengths
- Success vs failure rates
-
Context Preservation
- Context retrieval success rate
- Average conversation duration
- Context size statistics
-- Response time by endpoint
SELECT
clientRequestPath as endpoint,
AVG(edgeResponseTime) as avg_response_time,
COUNT(*) as request_count
FROM httpRequests
WHERE datetime > NOW() - INTERVAL '24' HOUR
GROUP BY endpoint
ORDER BY request_count DESC
-- Error rate by code
SELECT
responseHeaders['x-error-code'] as error_code,
COUNT(*) as error_count
FROM httpRequests
WHERE edgeResponseStatus >= 400
AND datetime > NOW() - INTERVAL '24' HOUR
GROUP BY error_code
ORDER BY error_count DESC
-- Cache effectiveness
SELECT
responseHeaders['x-cache-status'] as cache_status,
COUNT(*) as count,
AVG(edgeResponseTime) as avg_time
FROM httpRequests
WHERE datetime > NOW() - INTERVAL '1' HOUR
GROUP BY cache_statusSymptoms: Conversation context is lost between requests
Solution:
- Verify KV namespace binding in wrangler.toml
- Check X-Conversation-ID header is being sent
- Verify CONTEXT_STORE KV namespace has data:
wrangler kv:key list --namespace-id=<your-namespace-id>
Symptoms: Some operations succeed, others fail
Solution:
- Check individual service health
- Review error details in response
- Implement retry logic for failed operations
- Check rollback was executed properly
Symptoms: API responses taking > 2 seconds
Solution:
- Enable caching for GET requests
- Use batch operations instead of sequential calls
- Check upstream service health
- Review Cloudflare Worker CPU time metrics
- Always use composite endpoints when creating related entities
- Include X-Conversation-ID header for context preservation
- Use batch operations for multiple independent requests
- Handle partial success gracefully in GPT instructions
- Follow suggested next steps in responses
- Never expose sensitive details in error messages
- Always provide recovery suggestions
- Use appropriate HTTP status codes
- Log detailed errors server-side for debugging
- Cache read operations with appropriate TTLs
- Use parallel processing for independent operations
- Implement request timeouts (max 30 seconds for Workers)
- Monitor cache hit rates and adjust TTLs
After implementing these optimizations:
- OAuth 2.0 Support - Implement full OAuth flow for GPTs
- Webhook Callbacks - Add async operation support
- Advanced Context - ML-based context understanding
- Multi-Modal - Support image/file uploads in evidence
- Real-time Updates - SSE for live case updates
For questions or issues:
- Documentation:
/Users/nb/Projects/development/chittyconnect/CLAUDE.md - API Reference:
https://connect.chitty.cc/openapi-v2.json - Service Status:
https://connect.chitty.cc/api/services/status