logicspike/docs

gateway

Gateway — Route Reference

Last Updated: 2026-05-14 Status: Active Service: apps/gateway

Complete reference for every route the Gateway exposes. All routes are mounted under https://api.vlozi.app in production and http://localhost:8788 in local dev.


1. How Proxying Works

The Gateway does not re-encode or buffer request bodies. Every proxy route follows the same three-step pattern:

// 1. Build downstream headers (strips route prefix, injects identity)
const { headers, targetUrl } = buildDownstreamHeaders(c, "/prefix")
 
// 2. Forward via Service Binding (zero network overhead)
const response = await proxyFetch(SERVICE_BINDING, targetUrl, method, headers, body)
 
// 3. Stream response back to client
const [body, status, responseHeaders] = proxyResponseArgs(response)
return c.newResponse(body, status, responseHeaders)

The body is a ReadableStream passed through without buffering — SSE streams, large uploads, and chunked responses all work transparently.

1.1 Path Stripping

The Gateway mounts each proxy at a prefix (e.g. /blog) and strips that prefix before forwarding. The strip is anchored to the start of the path:

Gateway receives:  /blog/admin/posts
Stripped prefix:   /blog
Forwarded as:      /admin/posts

This is done by buildDownstreamHeaders() which uses path.startsWith(prefix) ? path.slice(prefix.length) : path.

1.2 Identity Headers Injected

All proxy requests that pass auth carry these headers to the downstream service:

Header Value Source
x-gateway-key GATEWAY_SECRET env var Shared secret; downstream workers reject requests without it
x-request-id crypto.randomUUID() Generated per request in auth middleware
x-tenant-id ctx.auth.tenant_id From JWT payload or API-key record
x-user-id ctx.auth.user_id From JWT payload; "system" for API-key requests
x-user-permissions JSON.stringify(ctx.auth.permissions) PBAC permission strings array
x-user-services JSON.stringify(ctx.auth.services) Service entitlement map

The host header is explicitly removed to prevent downstream services from rejecting requests due to mismatched host expectations.


2. Blog Proxy

File: src/routes/blog.proxy.ts Mounted at: /blog Downstream: BLOG_SERVICE (logicspike-blog-service)

2.1 Admin Door — /blog/admin/*

Requires: JWT → PBAC blog → Subscription active

The seller dashboard uses this door for all CMS operations: creating posts, managing tags, publishing, analytics.

# Example: list posts
GET /blog/admin/posts
Authorization: Bearer <jwt>

2.2 Public Door — /blog/public/*

Requires: API key (publishable or secret, blog:* permission)

Used by the @vlozi/blog SDK. The CORS policy for /blog/public/* is open (*), so any third-party website can call it from a browser with a publishable key.

# Example: fetch published posts for a tenant's site
GET /blog/public/posts?slug=my-post
x-api-key: pk_live_xxxxx

Publishable keys are enforced read-only (GET/HEAD only) with optional domain locking.

2.3 Debug / Test Endpoints (DEBUG mode only)

Path Purpose
GET /blog/debug Tests Service Binding connectivity to the blog worker
GET /blog/admin/test-log Triggers a test log entry

These return 404 when DEBUG !== "true".


3. Media Proxy

File: src/routes/media.proxy.ts Mounted at: /media Downstream: MEDIA_SERVICE (logicspike-media)

3.1 Protected Paths

Path pattern Auth required Sub guard
/media/upload/* JWT
/media/files/* JWT
Everything else (e.g. /media/health)

File uploads and file management are paid features. Public media delivery (if any) bypasses auth.

# Example: upload an image
POST /media/upload/image
Authorization: Bearer <jwt>
Content-Type: multipart/form-data

4. Manager Proxy

File: src/routes/manager.proxy.ts Mounted at: /manager Downstream: MANAGER_SERVICE (logicspike-manager)

The manager handles auth, billing, team management, and workspace settings. It uses a public-path exception model — most routes require a JWT, but specific paths are explicitly whitelisted as public.

4.1 Public Paths (no auth required)

These paths are matched against the path relative to /manager using strict equality (Set.has), not prefix matching. This prevents bypasses like /manager/admin/login accidentally matching /login.

Path Purpose
/register User registration
/login Email/password login
/auth/register Auth-flow registration
/auth/login Auth-flow login
/login/phone Phone login initiation
/login/phone/verify Phone OTP verification
/auth/oauth OAuth callback
/auth/refresh JWT refresh (validates old token internally)
/auth/verify-email Email ownership verification (pre-tenant)
/auth/verify-email/resend Resend email verification OTP
/auth/forgot-password Request password reset code
/auth/reset-password Submit reset code + new password
/onboarding/complete Post-registration onboarding (no JWT yet)
/billing/webhook Razorpay webhook (HMAC-validated inside manager)
/billing/plans Public pricing page data
/permissions/catalog PBAC permission catalog
/feedback Public feedback submission

4.2 Dynamic Public Paths

These are matched by regex (not exact string):

Pattern Purpose
GET /invitations/:token Team invitation landing page

4.3 Protected Paths

Everything else requires a JWT. The manager does not apply accessMiddleware or subscriptionGuard — tenants must always reach the billing UI to reactivate.

# Example: update workspace settings
PATCH /manager/workspace/settings
Authorization: Bearer <jwt>

5. Content Proxy

File: src/routes/content.proxy.ts Mounted at: /content Downstream: CONTENT_ENGINE_SERVICE (logicspike-content-engine)

Auth: JWT → PBAC content → Subscription active (all paths)

The content engine manages social media scheduling, AI content generation, and publish queues. All routes are protected.

GET /content/slots
Authorization: Bearer <jwt>

6. Brain Proxy

File: src/routes/brain.proxy.ts Mounted at: /brain Downstream: BRAIN_SERVICE (logicspike-brain-service)

Auth: JWT → PBAC brain → Subscription active (all paths)

The brain service handles AI chat, memory, and agent orchestration. Responses may be SSE streams — the proxy passes the ReadableStream body through unchanged.

# Example: start an AI chat session (SSE stream)
POST /brain/chat
Authorization: Bearer <jwt>
Content-Type: application/json
 
{ "message": "..." }

7. Contacts Proxy

File: src/routes/contacts.proxy.ts Mounted at: /contacts-intel Downstream: CI_SERVICE (logicspike-contact-intelligence)

Auth: JWT → PBAC contacts → Subscription active (all paths)

NOTE

The gateway route prefix is /contacts-intel but the path is stripped to / before forwarding, so the contact intelligence service receives paths without the prefix (e.g. /contacts-intel/contacts/123/contacts/123).

GET /contacts-intel/contacts
Authorization: Bearer <jwt>

8. Newsletter Proxy

File: src/routes/newsletter.proxy.ts Mounted at: /newsletter Downstream: NL_SERVICE (logicspike-newsletter)

This proxy has the most complex auth structure because it serves both the seller dashboard and third-party subscriber embeds.

8.1 Public Routes

Path Auth Notes
POST /newsletter/public/subscribe API key (newsletter:*) Tenant resolved from key; x-tenant-id injected
POST /newsletter/public/unsubscribe None at gateway HMAC token verified inside newsletter service
POST /newsletter/public/confirm None at gateway HMAC token verified inside newsletter service

The CORS policy for /newsletter/public/* is open (*) in index.ts — any website can embed a subscribe form.

# Example: subscribe a visitor
POST /newsletter/public/subscribe
x-api-key: pk_live_xxxxx
Content-Type: application/json
 
{ "email": "visitor@example.com", "listId": "lst_abc" }

8.2 Admin Routes

All other /newsletter/* paths require JWT → PBAC newsletter → Subscription active.

The admin guards use an inline path check to avoid re-running on already-matched /newsletter/public/ paths:

newsletterProxy.use("/*", async (c, next) => {
    if (c.req.path.startsWith("/newsletter/public/")) return next()
    return authMiddleware(c, next)
})
# Example: list campaigns
GET /newsletter/campaigns
Authorization: Bearer <jwt>

9. Comms Proxy

File: src/routes/comms.proxy.ts Mounted at: /comms Downstream: COMMS_SERVICE (logicspike-communication)

Auth: JWT → PBAC comms → Subscription active (all paths)

The comms service handles transactional email and SMS. Has an extra debug log in DEBUG mode that prints the first 8 + last 4 chars of GATEWAY_SECRET to verify the shared secret is wired correctly.

POST /comms/email/send
Authorization: Bearer <jwt>

10. Chatbot Proxy

File: src/routes/chatbot.proxy.ts Mounted at: /chatbot Downstream: CE_SERVICE (logicspike-chat-engine)

Auth: JWT → PBAC chatbot → Subscription active (all paths)

Like the brain proxy, responses may be SSE streams and are streamed through transparently.

POST /chatbot/sessions
Authorization: Bearer <jwt>

11. Global Endpoints

These are registered directly on the root Hono app in src/index.ts.

11.1 Health Check

GET /health

No auth. Returns 200 with binding presence map. Use to verify deployment.

{
  "status": "ok",
  "env": {
    "BLOG_SERVICE": true,
    "JWT_PUBLIC_KEY": true,
    "RATE_LIMIT_KV": true
  }
}

12. Error Response Reference

HTTP Status Scenario
401 Missing token Authorization header absent on a JWT-protected route
401 Invalid token JWT signature invalid, expired, or malformed
401 API key missing No x-api-key, Bearer, or ?api_key= on an API-key route
403 Invalid API key Key not found in DB, or status !== "active"
403 API key expired expiresAt is in the past
403 Publishable keys are read-only Non-GET with a publishable key
403 Domain not allowed Publishable key domain lock mismatch
403 API key has no permissions for service: X Key has no X:* permission
403 API key has read-only permissions for service: X Write attempt with a read-only key
402 NO_SUBSCRIPTION Tenant has no subscription row
402 TRIAL_EXPIRED Trial ended, no payment method
402 SUBSCRIPTION_CANCELED Subscription canceled
402 PAYMENT_FAILED past_due > 3 days grace period
429 Too many requests Rate limit exceeded (with Retry-After header)
500 Configuration error: X binding missing Worker binding not wired in wrangler.toml
502 Gateway failed to connect to X Service Binding fetch threw (Worker crashed or binding error)

gateway