Developer documentation

One local job API for the CLI signed in on your computer. Discover what is available, submit a request, then recover its result by job ID.

0.1.9
Source reference

The public 0.1.8 download is Codex-only. Claude and asset support are in the newer source and a local installation, not yet in a public release. Check discovery on your installed version.

On this page

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.

FeaturePublic 0.1.80.1.9 development source
Local Codex jobs, polling and idempotencyAvailableAvailable
Namespaced model, e.g. codex/<id>RequiredSupported
Explicit provider with unprefixed modelNot supportedSupported
Claude adapter and bounded attachmentsNot supportedImplemented; see verification limits below
Optional timeout error messageCode-only failuresHuman-readable recovery guidance
Remote tunnels / hosted inferenceNot implementedNot 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

  1. Open Makoda, check the provider’s installation/sign-in and create access for your project.
  2. Copy the setup instruction into your coding agent. Keep MAKODA_BASE_URL and MAKODA_TOKEN in private server-side configuration.
  3. Read the discovery routes. None of these reads submits model inference.
  4. 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.

RoutePurpose
GET /healthzGateway liveness
GET /v1/providersCached provider installation/auth readiness; no fresh CLI check or account quota disclosure
GET /v1/modelsExact model IDs, efforts, modalities and attachment types
GET /v1/capabilitiesInstalled gateway’s limits and execution mode
GET /v1/openapi.jsonInstalled gateway’s machine-readable contract
POST /v1/jobsSubmit one job
GET /v1/jobs/{id}Read that integration’s job and result
GET /v1/jobs/{id}/eventsSSE lifecycle events; not generated-text deltas
POST /v1/jobs/{id}/cancelRequest best-effort cancellation
GET /v1/usageRetained 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.

HTTP
POST /v1/jobs
Authorization: Bearer <MAKODA_TOKEN>
Content-Type: application/json
Idempotency-Key: <new-key-for-this-logical-request>
JSON
{
  "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:

JSON
{
  "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:

JSON
{
  "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:

JSON
{
  "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:

JSON
{
  "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:

JSON
{
  "error": {
    "code": "model_unavailable",
    "message": "model_unavailable",
    "retryable": false,
    "job_id": null,
    "details": {}
  }
}

Defaults and request validation

FieldDefault / requirement
providerNo default. Optional only when model is already namespaced, e.g. codex/gpt-6-sol. Supported explicit values: codex, claude.
modelRequired; must match discovery.
reasoning_effortRequired; must be supported by that model.
inputRequired; exactly one object with role: "user" and nonempty string content.
response_formatDefaults to {"type":"text"}. Optional json_schema format described below.
timeout_msDefaults to 180000 (3 minutes). Integer range 1000–900000 (1 second–15 minutes).
attachmentsDefaults to [].
Token limit / temperature / tool callingNo public request fields for these controls. Unknown fields are rejected.
Provider or model fallbackNone.
Conversation memoryNone 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:

JSON
{
  "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.

AssetSupported behavior
PNG / JPEGActual image input: private temporary image files for Codex; native base64 image blocks for Claude.
Plain text / Markdown / JSON / CSVUTF-8 text is included with labels in prompt context. MIME types: text/plain, text/markdown, application/json, text/csv.
PDF / DOCX / SVG / audio / videoUnsupported. Convert deliberately in your application or show an unsupported-format error.
Remote URLs / arbitrary filesystem pathsNot fetched or opened as attachments.
Generated image/video/downloadable binary artifactsNo binary output/export API. Responses remain text or validated JSON.

Scroll table to see all columns →

LimitValue
Files per request4
Decoded size per file4 MiB
Combined decoded attachments5 MiB
Entire JSON request including base64 overhead8 MiB
Prompt plus text attachment contents and framing200 KiB
Image dimensionsAt most 8,192 per dimension and 16,777,216 pixels
Attachment name1–128 UTF-8 bytes; no slashes/control characters; not a filesystem path
Text encodingUTF-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:

JSON
{
  "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:

SituationWhat it meansClient action
Submit HTTP request disconnects or times outAdmission 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 expiresYour 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):

JSON
{
  "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:

JSON
{
  "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.

FailureWhere it appears / response
Invalid tokenImmediate HTTP 401, unauthorized
Unsupported model/effortImmediate HTTP 400, model_unavailable / unsupported_effort
Conflicting provider/model prefixImmediate HTTP 400, provider_model_mismatch
Invalid timeoutImmediate HTTP 400, invalid_timeout
Invalid/unsupported attachmentImmediate HTTP 400, corresponding attachment error
Oversized request/context/filesImmediate HTTP 413, corresponding size error
Queue fullImmediate HTTP 429, queue_full; separate from provider quota
Same key with changed requestImmediate HTTP 409, idempotency_conflict
Recognized Claude throttlingAccepted job later fails with provider_rate_limited
Generic provider failureAccepted job later fails with provider_failed
Execution deadlineAccepted job later fails with timed_out
Expired queue waitAccepted job later fails with queue_expired
Unknown interruption/cancellation outcomeexecution_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.