Setup Adapter contract

Conduck Adapter Contract

v1 · stable

A normative specification, written to be handed to an AI coding tool. If you built your own AI agent and want the Conduck app to talk to it, paste this page (or its URL) into the tool that built your agent — e.g. Claude Code.

Raw markdown for your AI conduck.com/setup/adapter/v1.md

Prefer hands-off? There's a step-by-step build brief for AI tools — workflow, verification, and pairing included. Build it with your AI

Conduck Adapter Contract — v1

Status: v1, stable — document revision 1.3. This page is a normative specification, written to be handed to an AI coding tool. If you built your own AI and want the Conduck app to talk to it, paste this page (or its URL) into the tool that built your agent — e.g. Claude Code. There is also a step-by-step build brief written for AI tools that wraps this contract in a full workflow.

The key words MUST, MUST NOT, SHOULD, and MAY mark binding requirements and recommendations, in their usual (RFC 2119) sense. Everything else is explanation.

What you are building

Conduck is a native client — iPhone, iPad, Mac, Apple Watch, CarPlay, and Android — for an AI you run yourself. It speaks the OpenAI chat-completions HTTP format. Your agent stays exactly as it is — you add a small adapter in front of it: a separate HTTP service that receives Conduck’s request, runs ONE complete turn of your agent, and returns the final answer.

Division of labor — important:

The listening port is yours to choose — this contract fixes the loopback binding, not the port. Examples in this document use 8480.

Route requests by URL path; ignore any query string. Before building: inspect the existing project — don’t guess. If it already runs a long-lived HTTP server implementing the two routes below, nothing needs to be built.

Route 1 — GET /v1/models

Purpose: Conduck’s “Test Connection” probe, and model listing.

{"object": "list", "data": [{"id": "<your-agent-name>", "object": "model"}]}

Capability signaling (optional)

A model entry MAY carry an advisory, namespaced capability object:

{"id": "<your-agent-name>", "object": "model", "conduck": {"input_modalities": ["text", "image"]}}

Route 2 — POST /v1/chat/completions

The request Conduck sends:

{
  "messages": [
    {"role": "user", "content": "..."},
    {"role": "assistant", "content": "..."},
    {"role": "user", "content": "..."}
  ],
  "stream": false,
  "model": "<name>"
}

Images

Text-only engines — the complete recipe. An engine that cannot read images is a first-class, fully conformant configuration, not a degraded fallback. Do all three: (1) advertise no image capability on /v1/models; (2) reject a CURRENT-turn image with 400 + code image_unsupported; (3) replace EARLIER-message images with the in-position disclosure text above (never reject those requests). An adapter doing exactly this passes the doctor’s --deep image probe — the honest decline is a pass; only answering while silently ignoring an image fails.

Conversation snapshot

The response

{
  "choices": [
    {"message": {"role": "assistant", "content": "<final answer as plain text>"}}
  ]
}

Timing

Errors

Any failure: non-2xx status + a JSON body in the OpenAI error shape, extended with a stable machine-readable code:

{"error": {"message": "<short reason>", "type": "<kind>", "code": "<stable-code>"}}
codeMeaningStatus
image_unsupportedCurrent turn carries an image this adapter cannot accept400
model_not_foundSupplied model matches no advertised id400
context_too_longConversation exceeds what the engine can accept400
image_too_largeAn image exceeds a size cap413
overloadedBusy — a turn cannot start in time429 or 503
upstream_timeoutThe engine hit the internal deadline504
upstream_failureThe engine crashed or produced no usable result502

Status codes are normative:

StatusWhen
400Malformed body or fields — invalid JSON, empty messages, bad roles or shapes, plus the 400-coded cases above
401Missing or wrong token — checked FIRST, before any body processing
404Unknown path
405Known path, wrong method
413Request body exceeds your cap (see the floor below)
429 / 503Busy or overloaded
502Engine failure
504Engine timeout

HTTP-framing edge cases — a missing Content-Length, chunked request bodies — are deliberately NOT specified: let your HTTP stack handle them however it does. Conduck shows its own user-facing text keyed to the status and code — pick both carefully.

Authentication

Security floor (non-negotiable)

Self-test (before pairing)

Recommended check. On the machine where the adapter runs — here listening on port 8480, with TOKEN set — run these two commands (if your adapter binds a different port, change the URL in every command here to match; 8480 is only this document’s example):

curl -fsSLO https://github.com/gigaduckai/conduck-connect/releases/latest/download/conduck-connect.sh
CONDUCK_TOKEN="$TOKEN" bash conduck-connect.sh --doctor http://127.0.0.1:8480

The script changes nothing. It shows what passed and what to fix; green checks mean the tested requests and replies match what Conduck expects. To also check image handling and the deeper probes, run the second command once more with --deep added at the end.

Check by hand. These three commands show the replies directly, but cover only the basics:

curl -s -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8480/v1/models
# prints: {"object":"list","data":[{"id":"...","object":"model"}]}

curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8480/v1/models
# prints: 401

curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"messages":[{"role":"user","content":"Reply with exactly: pong"}],"stream":false}' \
  http://127.0.0.1:8480/v1/chat/completions
# prints: {"choices":[{"message":{"role":"assistant","content":"pong"}}]}

The pong round-trip mirrors what conduck-connect’s verify step runs.

File lane (optional profile)

Beyond chat, Conduck can exchange files with your agent through a folder on the adapter’s machine, served over WebDAV — conduck-connect offers to set this up and calls the folder “the agent’s working folder”. This lane is a deliberately OPTIONAL profile outside the core contract: skipping it is fine (chat works and attachments simply ride inline), and enabling it changes nothing about the two routes above. If you enable it:

Then connect it

On the machine where the adapter runs, run Conduck’s setup script with the --generic flag — https://conduck.com/setup/adapter/v1/ and the app’s guided setup for a custom gateway show the exact one-paste command:

curl -fsSLO https://github.com/gigaduckai/conduck-connect/releases/latest/download/conduck-connect.sh && bash conduck-connect.sh --generic

The flag matters: it skips gateway auto-detection, so another AI install on the machine can’t become the default. Give the script the adapter’s local port. It helps expose the adapter over HTTPS — through a Tailscale or cloudflared already on the machine, or an HTTPS front you already run; it installs nothing and never creates the TLS listener itself (with none of those present, Tailscale is the one-command unblock) — then verifies with real requests and prints a QR pairing code to scan in Conduck.

The wizard needs an interactive terminal: prompts cannot be piped, and there are no non-interactive answer flags. An AI tool driving it needs a real PTY (or hand the final pairing step to a human at a terminal).

Versioning

This is version 1 of the contract — document revision 1.3. This URL is stable. Before Conduck’s public launch, normative corrections may revise this document in place, always with a visible changelog entry below. After launch, any change that would make a conforming adapter nonconforming ships as /setup/adapter/v2 — v1 stays; pure clarifications may continue to land here.

Back to setup