Use one local job API to send a prompt, model and reasoning effort to an installed coding CLI. No Makoda npm package is required. Your application’s backend calls Makoda; Makoda uses the desktop owner’s provider sign-in. Inference consumes that account’s allowance.
Versions and compatibility
This reference describes the current 0.1.9 development source. Public Mac and Ubuntu downloads remain 0.1.8. Installing from the website does not yet install the Claude, attachment or improved timeout-message changes described here. Read the running gateway’s /v1/openapi.json, /v1/capabilities and /v1/models before enabling a feature.
| Feature | Public 0.1.8 | 0.1.9 development source |
|---|---|---|
| Local Codex jobs, polling and idempotency | Available | Available |
Namespaced model, e.g. codex/<id> | Required | Supported |
Explicit provider with unprefixed model | Not supported | Supported |
| Claude adapter and bounded attachments | Not supported | Implemented; see verification limits below |
| Optional timeout error message | Code-only failures | Human-readable recovery guidance |
| Remote tunnels / hosted inference | Not implemented | Not implemented |
Scroll table to see all columns →
Makoda uses HTTP, JSON, bearer tokens and OpenAPI 3.1. Its /v1/jobs interface is a custom asynchronous job contract, not a drop-in OpenAI Chat Completions, OpenAI Responses or Anthropic Messages API. It does not implement their messages, tool-calling, sampling or token-streaming formats. Unknown request fields are rejected. The contract version is 0.1.0; it is separate from the desktop application version.
Connect and discover
- Open Makoda, check the provider’s installation/sign-in and create access for your project.
- Copy the setup instruction into your coding agent. Keep
MAKODA_BASE_URLandMAKODA_TOKENin private server-side configuration. - Read the discovery routes. None of these reads submits model inference.
- Submit a job only when the application’s user requests it, then retain its ID and idempotency key.
The default base URL is http://127.0.0.1:43127; the owner can change the port in Settings. Use the endpoint shown by the app. Only /healthz is unauthenticated.
| Route | Purpose |
|---|---|
GET /healthz | Gateway liveness |
GET /v1/providers | Cached provider installation/auth readiness; no fresh CLI check or account quota disclosure |
GET /v1/models | Exact model IDs, efforts, modalities and attachment types |
GET /v1/capabilities | Installed gateway’s limits and execution mode |
GET /v1/openapi.json | Installed gateway’s machine-readable contract |
POST /v1/jobs | Submit one job |
GET /v1/jobs/{id} | Read that integration’s job and result |
GET /v1/jobs/{id}/events | SSE lifecycle events; not generated-text deltas |
POST /v1/jobs/{id}/cancel | Request best-effort cancellation |
GET /v1/usage | Retained job statistics scoped to the authenticated integration |
Scroll table to see all columns →
Operational requests use Authorization: Bearer <MAKODA_TOKEN>. Submission also requires Content-Type: application/json and exactly one Idempotency-Key header containing 1–128 printable non-space ASCII characters. A UUID is a convenient key. Never put the token in a URL, frontend bundle, logs or source control.
Provider readiness is not a guarantee of model entitlement, remaining quota or a successful model response. Discover IDs rather than deriving them from marketing names. Codex models come from its local model catalog. Claude currently exposes only sonnet and opus, each with low, medium and high effort. Those aliases follow the installed CLI’s mapping; they do not pin a model revision. Fable 5.1 is not currently accepted by Makoda, regardless of whether the CLI or account advertises it elsewhere.
Submit a Codex job
Examples below use fictional job IDs, timestamps and results. They do not show a new live request. Use gpt-6-sol with medium only when the local discovery response lists that combination.
POST /v1/jobs
Authorization: Bearer <MAKODA_TOKEN>
Content-Type: application/json
Idempotency-Key: <new-key-for-this-logical-request>{
"provider": "codex",
"model": "gpt-6-sol",
"reasoning_effort": "medium",
"input": [
{
"role": "user",
"content": "Explain what an API gateway does in one sentence."
}
],
"response_format": { "type": "text" },
"timeout_ms": 180000
}For public 0.1.8, omit provider and use "model": "codex/gpt-6-sol" instead. The namespaced form also works on 0.1.9. Do not change between these representations when replaying the same idempotency key: the original request body determines its fingerprint.
An accepted submission returns HTTP 202, with a Job object:
{
"id": "example-codex-job",
"integration_id": "example-project",
"model": "codex/gpt-6-sol",
"reasoning_effort": "medium",
"status": "queued",
"created_at": "2026-10-19T09:00:00Z",
"updated_at": "2026-10-19T09:00:00Z",
"result_expires_at": null,
"idempotency_expires_at": "2026-10-26T09:00:00Z",
"result_availability": "pending",
"result": null,
"error": null,
"execution_mode": "trusted_local_agent",
"adapter_version": "codex-exec/0.1.0",
"metrics": { "latency_ms": null, "usage": null }
}Persist id. Poll GET /v1/jobs/example-codex-job with the same bearer token until status is succeeded, failed or cancelled. An illustrative successful HTTP 200 response:
{
"id": "example-codex-job",
"integration_id": "example-project",
"model": "codex/gpt-6-sol",
"reasoning_effort": "medium",
"status": "succeeded",
"created_at": "2026-10-19T09:00:00Z",
"updated_at": "2026-10-19T09:00:10Z",
"result_expires_at": "2026-10-20T09:00:10Z",
"idempotency_expires_at": "2026-10-26T09:00:00Z",
"result_availability": "available",
"result": {
"provider": "codex",
"text": "An API gateway receives client requests and routes them to the appropriate backend service.",
"usage": null,
"cli_version": "<reported CLI version>"
},
"error": null,
"execution_mode": "trusted_local_agent",
"adapter_version": "codex-exec/0.1.0",
"metrics": { "latency_ms": 10000, "usage": null }
}result.usage contains provider-reported counters when available. null or a missing counter means unknown, not zero. Counters can include provider/agent overhead and are not an estimate of subscription percentage or price. metrics.latency_ms includes queue time. The requested model is not proof of the provider’s resolved model revision.
Submit a Claude job
Use the same endpoint and headers, with a separately chosen idempotency key:
{
"provider": "claude",
"model": "opus",
"reasoning_effort": "low",
"input": [
{
"role": "user",
"content": "Explain what an API gateway does in one sentence."
}
],
"response_format": { "type": "text" },
"timeout_ms": 180000
}An illustrative successful result excerpt, with job bookkeeping omitted:
{
"id": "example-claude-job",
"model": "claude/opus",
"reasoning_effort": "low",
"status": "succeeded",
"result": {
"provider": "claude",
"text": "An API gateway directs incoming requests to the services that handle them.",
"usage": null,
"cli_version": "<reported CLI version>"
},
"error": null
}Sending "model": "fable-5.1" would currently return HTTP 400 before inference. Makoda does not substitute Opus or Sonnet:
{
"error": {
"code": "model_unavailable",
"message": "model_unavailable",
"retryable": false,
"job_id": null,
"details": {}
}
}Defaults and request validation
| Field | Default / requirement |
|---|---|
provider | No default. Optional only when model is already namespaced, e.g. codex/gpt-6-sol. Supported explicit values: codex, claude. |
model | Required; must match discovery. |
reasoning_effort | Required; must be supported by that model. |
input | Required; exactly one object with role: "user" and nonempty string content. |
response_format | Defaults to {"type":"text"}. Optional json_schema format described below. |
timeout_ms | Defaults to 180000 (3 minutes). Integer range 1000–900000 (1 second–15 minutes). |
attachments | Defaults to []. |
| Token limit / temperature / tool calling | No public request fields for these controls. Unknown fields are rejected. |
| Provider or model fallback | None. |
| Conversation memory | None between jobs. |
Scroll table to see all columns →
For a chat UI, keep conversation history in your own application and construct the required context inside the single user message. Multiple input messages and separate system/assistant roles are not accepted. Keep the constructed text within the size limit; silently dropping history can change the answer.
The example client scripts may supply convenience defaults such as medium effort. Those defaults belong to those scripts, not to the HTTP API.
Images and documents
On 0.1.9, append attachments to the same request. Encode actual bytes as standard base64, without a data: URL prefix:
{
"provider": "codex",
"model": "gpt-6-sol",
"reasoning_effort": "medium",
"input": [
{
"role": "user",
"content": "Describe this screenshot and suggest two UI improvements."
}
],
"attachments": [
{
"name": "screenshot.png",
"media_type": "image/png",
"data_base64": "<actual base64-encoded PNG bytes>"
}
]
}The placeholder is not executable input. Check the selected model’s input_modalities and attachment_media_types first. Your backend should validate the file and encode it; browsers should not hold Makoda credentials.
| Asset | Supported behavior |
|---|---|
| PNG / JPEG | Actual image input: private temporary image files for Codex; native base64 image blocks for Claude. |
| Plain text / Markdown / JSON / CSV | UTF-8 text is included with labels in prompt context. MIME types: text/plain, text/markdown, application/json, text/csv. |
| PDF / DOCX / SVG / audio / video | Unsupported. Convert deliberately in your application or show an unsupported-format error. |
| Remote URLs / arbitrary filesystem paths | Not fetched or opened as attachments. |
| Generated image/video/downloadable binary artifacts | No binary output/export API. Responses remain text or validated JSON. |
Scroll table to see all columns →
| Limit | Value |
|---|---|
| Files per request | 4 |
| Decoded size per file | 4 MiB |
| Combined decoded attachments | 5 MiB |
| Entire JSON request including base64 overhead | 8 MiB |
| Prompt plus text attachment contents and framing | 200 KiB |
| Image dimensions | At most 8,192 per dimension and 16,777,216 pixels |
| Attachment name | 1–128 UTF-8 bytes; no slashes/control characters; not a filesystem path |
| Text encoding | UTF-8 without NUL |
Scroll table to see all columns →
Invalid attachments are rejected before dispatch. Uploaded request content is retained locally while the job is nonterminal and removed with the request at completion; avoid treating this as an encrypted upload vault. Temporary Codex image files are cleaned on completion/failure/cancellation.
Structured JSON output
The outer HTTP response is always JSON. To require the model’s output to follow a schema, supply response_format:
{
"type": "json_schema",
"schema": {
"type": "object",
"properties": { "summary": { "type": "string" } },
"required": ["summary"],
"additionalProperties": false
}
}On success, read result.json; result.text also contains the model’s JSON text. Makoda validates the output. Invalid output fails with output_invalid; unsupported schemas fail with schema_unsupported. This is a bounded JSON Schema subset: at most 64 KiB, depth 16 and 2,048 nodes, with no external references. See the installed contract and validator before relying on advanced schema keywords.
Heavy jobs and timeout handling
Choose the execution budget before submitting expensive work. For a heavier task, an application can set "timeout_ms": 600000 (10 minutes), up to 900000 (15 minutes). Leave space in the client’s overall wait budget for queueing and provider startup as well. A larger timeout allows more time; it does not guarantee an answer, refund tokens or increase available provider allowance.
There are three different timeouts:
| Situation | What it means | Client action |
|---|---|---|
| Submit HTTP request disconnects or times out | Admission may already have happened. | Replay the exact same body with the same idempotency key to recover the job. Do not increase timeout_ms on this replay. |
| Polling/client wait deadline expires | Your application stopped waiting; the job may still be running. | Keep the job ID and read it again. Do not create a replacement job automatically. |
Job reaches terminal failed with error.code: "timed_out" | Makoda’s local execution deadline elapsed, or the provider reported a timeout. On a local deadline Makoda attempts to stop its CLI; upstream outcome/allowance consumption may be unknown. | Show the error, inspect the existing job, and let the user choose a deliberate new attempt with a larger budget and a new key when appropriate. A larger budget cannot prevent all provider-side timeouts. |
Scroll table to see all columns →
Timeout errors in new builds include an optional human-readable error.message explaining the need to increase timeout_ms for heavy jobs and the possibility that allowance was already consumed. Older jobs/builds may contain only the stable error.code; clients need a fallback explanation. Branch on the code, not the exact message text.
An illustrative terminal timeout response excerpt (polling still returns HTTP 200):
{
"id": "example-heavy-job",
"status": "failed",
"result": null,
"error": {
"code": "timed_out",
"message": "This job timed out. Heavy work may need a higher timeout_ms before a deliberate new attempt (default 180000 ms; maximum 900000 ms). This attempt may have consumed provider allowance; upstream completion and output are unknown. Inspect the existing job first. Recover a lost response with the same idempotency key and unchanged body; a new attempt with a longer timeout needs a new key and may consume more allowance. A higher timeout_ms does not prevent provider-side timeouts or guarantee output."
}
}Changing the timeout changes the request. Reusing the old key with the changed body returns an idempotency conflict; reusing the old key and original body returns the original failed job and does not resume it. Creating a new key starts new work and may consume more allowance. There is no resume, refund or automatic retry guarantee.
The gateway admits at most 8 queued jobs and executes one job at a time across providers. Queue wait is separate from timeout_ms. When the worker next reaches a queued job, it rejects work older than about 120 seconds with queue_expired; a job waiting behind a long run can remain queued beyond that age before the check happens. Do not expect a terminal error exactly at 120 seconds. Increasing a job’s execution timeout does not extend this queue age limit. Cancelling a job is best effort and likewise does not prove upstream work stopped without cost.
Job lifecycle, replay and retention
Jobs move from queued to running, then succeeded, failed or cancelled; cancelling is an intermediate state. Polling a failed job is still HTTP 200 because reading the job succeeded. Always inspect status, then result or error. Successful submission is not successful generation.
Deduplication lasts 7 days. Replaying the same key and body returns the same job, including after a failure; it does not start another inference. Retain the key, request body and returned ID in your application. A deliberate new action should have a new key. Do not change provider, model representation, effort, timeout or attachments during recovery of an uncertain submission.
Completed output is retained for up to 24 hours. When result_availability is expired, Makoda does not regenerate it. Account allowance is independent of result retention. Lifecycle SSE is available at /v1/jobs/{id}/events, with event IDs for reconnection; generated text is final-only, not token-streamed.
Failures, allowance and fallback
Makoda has no automatic cross-provider or model fallback and does not automatically resubmit failed jobs. The tray’s account-wide quota is informational, can be stale, and does not currently gate job admission. A provider may reject a request even after readiness checks pass.
If Claude reports a recognizable rate-limit failure, the job may look like this excerpt:
{
"id": "example-claude-job",
"model": "claude/opus",
"reasoning_effort": "low",
"status": "failed",
"result": null,
"error": { "code": "provider_rate_limited" }
}A rate limit is not proof of zero subscription allowance. Claude maps recognizable structured errors; Codex currently collapses many upstream failures, including possible quota failures, to provider_failed. Ambiguous failures can become timed_out. There is no reliable universal quota_exhausted code yet. Do not display a guessed remaining balance or reset time from these failures.
| Failure | Where it appears / response |
|---|---|
| Invalid token | Immediate HTTP 401, unauthorized |
| Unsupported model/effort | Immediate HTTP 400, model_unavailable / unsupported_effort |
| Conflicting provider/model prefix | Immediate HTTP 400, provider_model_mismatch |
| Invalid timeout | Immediate HTTP 400, invalid_timeout |
| Invalid/unsupported attachment | Immediate HTTP 400, corresponding attachment error |
| Oversized request/context/files | Immediate HTTP 413, corresponding size error |
| Queue full | Immediate HTTP 429, queue_full; separate from provider quota |
| Same key with changed request | Immediate HTTP 409, idempotency_conflict |
| Recognized Claude throttling | Accepted job later fails with provider_rate_limited |
| Generic provider failure | Accepted job later fails with provider_failed |
| Execution deadline | Accepted job later fails with timed_out |
| Expired queue wait | Accepted job later fails with queue_expired |
| Unknown interruption/cancellation outcome | execution_outcome_unknown; do not assume no allowance was consumed |
Scroll table to see all columns →
HTTP-level error envelopes include code, message, retryable, job_id and details. Terminal job errors have a stable code and may include a human-readable message. Neither format authorizes automatic inference retries. CLI internals and structured-output repair can involve additional upstream attempts; one Makoda job is not a promise of exactly one provider API request.
Detected API-key/cloud/custom provider configurations are rejected by the subscription adapters. However, provider-managed extra usage/credits may still be billed if enabled on the owner’s account. Makoda does not enforce a hard provider spending cap. Project statistics and token counters must not be presented as remaining subscription quota or a dollar bill.
Local security and verification limits
The gateway binds IPv4 loopback and validates the exact Host header. Browser-origin requests are rejected. A browser app should call its own backend, and that backend should call Makoda using a private project token. A hosted backend cannot reach a user’s computer through its own 127.0.0.1. Remote signup, pairing and tunnels remain future work.
These are coding-agent CLIs, not a proven tool-free model sandbox. Admit trusted local applications only. Project credentials are revocable and scoped to their own jobs; desktop account allowance is owner-only metadata, not exposed by /v1/providers or /v1/usage.
Codex text inference has passed authorized live checks. The local Claude adapter, images and structured output have offline fixture coverage, but the latest authorized Claude text checks failed/timed out; live Claude completion, live image understanding and broad model/effort coverage remain unverified. A successful quota read does not prove that generation will work.
Use the running gateway’s OpenAPI as the integration authority. The checked-in contract and this document describe development source and may be newer than an installed public binary.