Gateway /

API Reference

v4
10 endpoints docs v4.4 · updated 2026-06-01
Generate Endpoint Comparison Side-by-side differences between the three inference endpoints
/api/generate
no auth
/api/generate_v2
auth required
/api/generate_async
auth · v4 new
Auth required None API key API key
HTTP status on success 200 OK 200 OK 202 Accepted
Blocks caller Yes — sync Yes — sync No — returns immediately
Result delivery Response body Response body Callback + poll
Redis cache Never (always fresh)
Logs api_key_id
model optional
DB default fallback
optional
DB default fallback
required
prompt required required required
callback_url required
callback_secret optional
system optional optional optional
context optional optional
always []
options optional optional optional
images optional optional optional
no_cache optional optional
stream accepted, ignored accepted, ignored
use_fallback optional
default: true
optional
fallback_model optional optional
api_key body field optional optional
Best for Internal / trusted-network callers that don't need per-key tracking Authenticated clients where per-key usage logging matters Long-running inference or fire-and-forget jobs — caller doesn't wait
POST /api/generate Run inference — Ollama-compatible generate endpoint

Routes the request to the correct provider (Ollama / Gemini / Groq) based on the model name. Checks Redis cache first; on a miss, calls the provider and caches the result. Response shape is identical to Ollama's /api/generate so existing clients need no changes.

Request Body application/json
Field Type Description
modeloptional string Model ID as registered — e.g. gemma3:4b, gemini-2.0-flash, llama-3.3-70b-versatile  default: DB default_model setting
promptrequired string User input text
systemoptional string System instruction prepended to the conversation  default: ""
contextoptional number[] Ollama conversation context tokens from a previous response; ignored by cloud providers  default: []
optionsoptional object Inference params: { temperature?, top_p?, num_predict? }  default: {}
imagesoptional string[] Base64-encoded images for multimodal models (moondream, gemini-pro-vision, etc.)  default: []
no_cacheoptional boolean Set true to bypass Redis cache and force a fresh inference call  default: false
streamoptional boolean Logged only — streaming not implemented; always returns full response  default: false
Success Response 200
{
  model:             string,   // Model used (may differ if default was applied)
  created_at:        string,   // Timestamp in app timezone (Asia/Kuala_Lumpur)
  response:          string,   // Model text reply
  done:              true,     // Always true (non-streaming)
  context:           number[], // Ollama context tokens; [] for cloud providers
  total_duration:    number,   // Duration in nanoseconds (ms x 1,000,000)
  load_duration:     number,   // Ollama model load ns; 0 for cloud providers
  prompt_eval_count: number,   // Input token count
  eval_count:        number    // Output token count
}
Error Responses
429 Provider rate limit exceeded — back off and retry
503 Provider unreachable (ECONNREFUSED / ETIMEDOUT)
500 Unexpected server error — check server logs
Notes
  • Cache key is MD5 of { model, prompt, system, context, images, options }. Any field change forces a cache miss.
  • Model status is updated to "unavailable" in memory if this call fails (except 429 rate limits — those are transient).
  • Response is sent before Redis cache write and MySQL log to keep client-visible latency minimal.
POST /api/generate_v2 Authenticated inference with optional auto-fallback — requires an API key

Extends POST /api/generate with API key authentication and automatic model fallback. Requires a valid API key; stamps api_key_id on the api_logs row for per-key usage tracking. Fallback is on by default (use_fallback=true): if the primary model fails, the proxy retries with fallback_model (or proxy_settings.default_fallback_model) and returns a 200 with extra fallback fields. The primary model is marked unavailable regardless of whether the fallback succeeds. When both models fail, the error response includes a combined fallback_reason in api_logs: primary[<model>]: <err> | fallback[<model>]: <err>. The api_key body field (if supplied) is stripped before the request is forwarded to the provider.

Request Body application/json
Field Type Description
modeloptional string Model ID as registered — e.g. gemma3:4b, gemini-2.0-flash, llama-3.3-70b-versatile  default: DB default_model setting
promptrequired string User input text
systemoptional string System instruction prepended to the conversation  default: ""
contextoptional number[] Ollama conversation context tokens from a previous response; ignored by cloud providers  default: []
optionsoptional object Inference params: { temperature?, top_p?, num_predict? }  default: {}
imagesoptional string[] Base64-encoded images for multimodal models  default: []
no_cacheoptional boolean Set true to bypass Redis cache and force a fresh inference call  default: false
streamoptional boolean Logged only — streaming not implemented; always returns full response  default: false
use_fallbackoptional boolean Retry with a fallback model when the primary model fails. Defaults to true (opt-out to disable). Fallback is resolved from fallback_model if set, otherwise from proxy_settings.default_fallback_model.  default: true
fallback_modeloptional string Explicit fallback model ID. If omitted (and use_fallback is true), the proxy reads proxy_settings.default_fallback_model. Must differ from model.  default: null
api_keyoptional string Alternative to header-based auth — stripped before forwarding to provider
Success Response 200
// Primary model succeeded (or use_fallback=false):
{
  model:             string,
  created_at:        string,
  response:          string,
  done:              true,
  context:           number[],
  total_duration:    number,
  load_duration:     number,
  prompt_eval_count: number,
  eval_count:        number
}

// Fallback model was used (primary failed, fallback succeeded):
{
  model:             string,   // originally requested model
  actual_model:      string,   // fallback model that actually ran
  fallback_used:     true,
  fallback_reason:   string,   // primary-model error that triggered fallback
  created_at:        string,
  response:          string,
  done:              true,
  context:           number[],
  total_duration:    number,
  load_duration:     number,
  prompt_eval_count: number,
  eval_count:        number
}
Error Responses
401 Missing or invalid API key
429 Provider rate limit exceeded — back off and retry
503 Provider unreachable or both primary and fallback failed
500 Unexpected server error — check server logs
Notes
  • API key lookup order: Authorization: Bearer <key> | X-Api-Key: <key> | api_key body field.
  • use_fallback defaults to true — unlike /api/generate_async which defaults to false (opt-in).
  • markModelUnavailable() is called for the primary model on any failure (except 429 / policy errors), even when the fallback succeeds.
  • Fallback result is cached under the fallback model's own cache key, not the primary model's key.
  • When both primary and fallback fail, api_logs.fallback_reason contains both errors: "primary[<model>]: <err> | fallback[<model>]: <err>".
  • api_key_id is written to the api_logs row; use the Admin Console Token Usage tab to view per-key stats.
POST /api/generate_async NEW · v4 Async inference — queues job and returns immediately; result delivered via callback

Enqueues an inference job and returns 202 with a job_id immediately — the client does not wait for the model to respond. The in-process job worker picks up the job from Redis, runs inference via routeGenerate(), stores the result in async_jobs, then POSTs to callback_url. Requires a valid API key. Callback delivery retries up to proxy_settings.callback_retry_max times (default 0 = no retry), waiting proxy_settings.callback_retry_delay_sec seconds between each attempt (default 30 s). Retries run detached and do not block new jobs. If all attempts fail, callback_failed is set to true and the client can poll GET /api/jobs/:id. Optional fallback: set use_fallback=true to automatically retry with a different model if the primary fails.

Request Body application/json
Field Type Description
modelrequired string Primary model ID — same format as /api/generate (e.g. gemma3:4b, gemini-2.0-flash). No DB default fallback — must be supplied explicitly.
promptrequired string User input text sent to the model
callback_urlrequired string Absolute URL the worker POSTs the result to when inference completes
callback_secretoptional string If set, sent as the X-Callback-Secret header on this job's callback POST so the receiver can authenticate it. Max 128 chars. Stored on the job row, never returned by GET /api/jobs/:id. Takes precedence over the ASYNC_CALLBACK_SECRET env var.
systemoptional string System instruction prepended to the prompt  default: ""
optionsoptional object Inference params: { temperature?, top_p?, num_predict? }  default: {}
imagesoptional string[] Base64-encoded images for vision models  default: []
use_fallbackoptional boolean If true, retry with a fallback model when the primary model fails. Fallback is resolved from fallback_model if set, otherwise from proxy_settings.default_fallback_model.  default: false
fallback_modeloptional string Explicit fallback model ID. If omitted (and use_fallback is true), the worker reads proxy_settings.default_fallback_model. Must differ from model.  default: null
api_keyoptional string Alternative to header-based auth — stripped before processing, never forwarded to provider or stored
Success Response 202
{
  job_id:   string,  // UUID — use this to poll GET /api/jobs/:id
  status:   "queued",
  model:    string,
  poll_url: string   // convenience URL: /api/jobs/
}
Error Responses
400 Missing model, prompt, or callback_url; or callback_url is not a valid URL
401 Missing or invalid API key
Notes
  • Callback POST body shape: { job_id, status, model, requested_model, fallback_used, fallback_reason, provider, response, error, prompt_tokens, completion_tokens, duration_ms, completed_at } — model is the actual model that ran; requested_model is non-null only when fallback was used.
  • If both primary and fallback fail, job status is "failed". fallback_reason contains both errors: "primary[<model>]: <err> | fallback[<model>]: <err>". error contains only the fallback error message.
  • Callback retry is controlled by two proxy_settings rows: callback_retry_max (Y — max retries, default 0 = no retry) and callback_retry_delay_sec (X — seconds between retries, default 30). callback_attempts in async_jobs records how many delivery attempts were made. Retries run detached from the job worker loop so they never block new jobs.
  • Worker concurrency: 3 (ASYNC_JOB_CONCURRENCY env). Queue Redis key: async:jobs:queue
  • API key lookup: Authorization: Bearer <key> | X-Api-Key: <key> | api_key body field (stripped before processing).
  • callback_url must be an absolute URL reachable from inside the Docker container — not localhost or 127.0.0.1 (those resolve to the container itself, not your machine). Examples: http://host.docker.internal:8080/webhook — Windows/Mac Docker Desktop (reaches host machine); http://172.17.0.1:8080/webhook — Linux Docker host (default bridge gateway IP); http://<LAN-IP>:8080/webhook — any machine on the same network; http://other-container:8080/webhook — another service on the same Docker network.
GET /api/jobs/:id NEW · v4 Poll async job status and retrieve result when complete

Returns the current state of an async job created via POST /api/generate_async. Requires the same API key that created the job — cross-key access is rejected with 404. Use this endpoint when callback delivery is uncertain (callback_failed: true) or when your client cannot receive inbound HTTP callbacks.

Query Parameters
Field Type Description
?:idrequired string (path) Job UUID returned by POST /api/generate_async
Success Response 200
{
  job_id:            string,
  status:            "queued" | "processing" | "completed" | "failed",
  model:             string,          // actual model that ran (fallback model if fallback was used)
  requested_model:   string | null,   // original requested model — non-null only when fallback was used
  fallback_used:     boolean,         // true if fallback inference was triggered
  fallback_reason:   string | null,   // primary error; or "primary[m]: e | fallback[m]: e" when both fail
  provider:          string | null,   // OLLAMA | GEMINI | GROQ | null (while queued)
  created_at:        string,
  started_at:        string | null,
  completed_at:      string | null,
  response:          string | null,   // inference result when status=completed
  error:             string | null,   // error detail when status=failed
  prompt_tokens:     number,
  completion_tokens: number,
  duration_ms:       number,
  callback_url:      string,
  callback_failed:   boolean,         // true if all callback delivery attempts failed
  callback_attempts: number           // how many delivery attempts were made (0 = not yet attempted)
}
Error Responses
401 Missing or invalid API key
404 Job not found or belongs to a different API key
Notes
  • Poll until status is "completed" or "failed" — processing can take seconds to minutes.
  • response is null while the job is queued or processing.
  • When fallback_used is true: model holds the fallback model ID, requested_model holds the original, fallback_reason holds the primary error.
  • callback_attempts counts delivery attempts: 1 = first try succeeded or failed with no retries configured; 2+ = at least one retry was made. 0 means callback delivery has not been attempted yet.
  • If callback_failed is true, the job result is still available here — poll GET /api/jobs/:id to retrieve it.
GET /api/models Model inventory — all registered models grouped by provider

Returns the in-memory model registry built at startup by loadModelRegistry(). The .providermodelignore list is already applied — ignored models do not appear. No external API calls; instant read from memory.

Success Response 200
{
  ollama: [{ name: string, id: string, size: number, provider: "ollama", details: object }],
  gemini: [{ name: string, id: string,               provider: "gemini", details: object }],
  groq:   [{ name: string, id: string,               provider: "groq",   details: object }]
}
Error Responses
405 Method not allowed — only GET is accepted
Notes
  • To refresh after adding a new Ollama model or provider changes, restart the server.
GET /api/status Live health status of every registered model

Returns the current in-memory statusMap — updated by the health check scheduler and reactively by generate failures. No probing is performed on this call; response is instant.

Success Response 200
{
  lastUpdated: string,
  summary: {
    total: number, available: number,
    unavailable: number, pending: number, ignored: number
  },
  providers: {
    ollama: [{ modelId, provider, status, latencyMs, lastChecked, error }],
    gemini: [...],
    groq:   [...]
  }
}
// status: "available" | "unavailable" | "pending" | "ignored"
Notes
  • "pending" — registered but not yet probed (only possible in normal/once mode before first cycle completes).
  • "ignored" — present in .providermodelignore.
  • Probe uses the prompt defined by MODEL_HEALTH_CHECK_PROBE with MODEL_HEALTH_CHECK_DELAY_MS between each model probe to avoid provider rate limits.
  • When MODEL_HEALTH_CHECK_INTERVAL_SEC=-1 (bypass mode): all models start as "available", no probes are ever run. Status flips to "unavailable" only when POST /api/generate fails (excluding 429s).
  • When MODEL_HEALTH_CHECK_INTERVAL_SEC=0: one startup probe cycle only, no periodic re-checks.
  • When MODEL_HEALTH_CHECK_INTERVAL_SEC>0: startup probe + automatic re-check every N seconds.
GET /api/metrics Per-model live usage metrics — rpm, tpm, rpd, latency, totals

Returns rolling usage counters from the in-memory metrics store. All values reset on server restart. Optional query params filter the response.

Query Parameters
Field Type Description
?provideroptional string Filter to one provider: ollama | gemini | groq
?modelIdoptional string Filter to a single model ID (must be used together with provider)
?flatoptional "1" Return a flat array instead of the grouped object
Success Response 200
// Default grouped response:
{
  lastUpdated: string,
  providers: {
    ollama: [{
      modelId, provider,
      rpm,  tpm,  rpd,
      avgLatencyMs,     // rolling 60-second avg (successful only)
      avgLatencyHourMs, // rolling 1-hour avg   (successful only)
      totalRequests, totalTokens, errorCount, lastUsed
    }],
    gemini: [...], groq: [...]
  }
}
// With ?flat=1  → { lastUpdated, models: [...same fields...] }
// With ?provider=X&modelId=Y → { lastUpdated, model: {...} }
Error Responses
404 No metrics found for the given provider + modelId
Notes
  • rpm / tpm — rolling 60-second window.
  • avgLatencyMs — rolling 60-second average, successful requests only.
  • avgLatencyHourMs — rolling 1-hour average, successful requests only.
  • rpd — requests today, resets at midnight UTC+8.
  • totalRequests / totalTokens / errorCount — lifetime since last restart.
POST /api/rollup Manually trigger a token-usage rollup into the summary table

Runs the same incremental rollup that the background scheduler executes automatically every TOKEN_ROLLUP_INTERVAL_MIN minutes. Reads unprocessed rows from api_logs and aggregates them into token_usage_summary. Idempotent — safe to call multiple times.

Success Response 200
{ success: true, processed: number }  // rows rolled up this call
Error Responses
500 Rollup failed — check MySQL connectivity and server logs
Notes
  • Called by the Admin Console "Sync Now" button in the Token Usage tab.
  • No request body is required or read.
GET / Gateway diagnostic page — health, metrics, and status

Self-contained HTML page generated from in-memory state on every request. No DB or network calls. Auto-refreshes every 60 seconds by default (configurable interval with pause option).

Success Response 200
Content-Type: text/html — the gateway diagnostic page
GET /docs This API Reference page

Standalone HTML page listing all endpoint contracts. No DB or network calls.

Success Response 200
Content-Type: text/html — this page
Back to Gateway © 2026 ME-Tech Solution Sdn Bhd · All rights reserved