# Languages (/docs/sdks/languages)



The three SDKs are contract-locked — the same round-trip tests assert their
opcode envelopes, protocol types, and HMAC signatures are byte-identical. That
means the *protocol* surface is uniform. What differs is how complete each
implementation is and which HTTP frameworks each one can serve behind.

## At a glance [#at-a-glance]

|                                      | Go                               | TypeScript    | Python          |
| ------------------------------------ | -------------------------------- | ------------- | --------------- |
| Package                              | `github.com/ductor-io/ductor-go` | `@ductor/sdk` | `ductor` (PyPI) |
| Version                              | —                                | `0.1.0`       | —               |
| `execute` (run steps)                | ✅                                | ✅             | ❌ **HTTP 501**  |
| `discover` / `health-check` / `code` | ✅                                | ✅             | ✅               |
| Protocol types + HMAC                | ✅                                | ✅             | ✅ (round-trips) |

<Callout type="warn" title="Python execution is not supported">
  Go and TypeScript can execute workflows end to end. Python supports
  registration, discovery, health checks, protocol types, and signing, but its
  `execute` action returns HTTP 501.
</Callout>

## Per-language support [#per-language-support]

<Tabs items="[&#x22;Go&#x22;, &#x22;TypeScript&#x22;, &#x22;Python&#x22;]">
  <Tab value="Go">
    Module `github.com/ductor-io/ductor-go` (Go 1.25). Install and import the
    `ductor` package; serve adapters live in the `ductor/serve` subpackage.

    ```bash
    go get github.com/ductor-io/ductor-go
    ```

    ```go title="main.go"
    import (
        "net/http"

        "github.com/ductor-io/ductor-go/ductor"
        "github.com/ductor-io/ductor-go/ductor/serve"
    )

    client := ductor.NewClient(ductor.ClientOptions{
        AppID:      "checkout",
        SigningKey: []byte(os.Getenv("DUCTOR_SIGNING_KEY")),
    })
    // register workflows on client...

    http.Handle("/api/ductor", serve.NetHTTP(client))
    ```

    Serve adapters (`sdks/go/ductor/serve/`):

    | Adapter         | Status       | Notes                                                                                         |
    | --------------- | ------------ | --------------------------------------------------------------------------------------------- |
    | `serve.NetHTTP` | Production   | Stdlib `http.Handler`. Mount on any path.                                                     |
    | `serve.Lambda`  | Production   | AWS API Gateway proxy integration; handles base64 bodies and case-insensitive headers.        |
    | `serve.Connect` | Pass-through | Forwards to `NetHTTP` under a Connect-conventional path prefix. **Not** a Connect-RPC bridge. |
  </Tab>

  <Tab value="TypeScript">
    Package `@ductor/sdk`, version `0.1.0`. It has full protocol parity with Go —
    `Client`, the `step` API (`run`, `sleep`, `sleepUntil`, `waitForEvent`,
    `waitForSignal`, `invoke`), opcode types, and the HMAC `sign`/`verify` helpers.

    ```bash
    npm install @ductor/sdk
    ```

    Serve adapters are published as package subpath exports (from `package.json`):

    | Import                      | Framework   |
    | --------------------------- | ----------- |
    | `@ductor/sdk/serve/express` | Express     |
    | `@ductor/sdk/serve/hono`    | Hono        |
    | `@ductor/sdk/serve/next`    | Next.js     |
    | `@ductor/sdk/serve/nethttp` | Node `http` |
  </Tab>

  <Tab value="Python">
    Package `ductor` on PyPI. Install the core, or the `fastapi` extra for the
    Starlette/FastAPI adapters.

    ```bash
    pip install ductor          # core
    pip install ductor[fastapi] # + FastAPI/Starlette adapters
    ```

    You serve it through the ASGI adapter, which works with any ASGI 3.0 server —
    FastAPI, Starlette, Uvicorn, or Hypercorn (`ductor.serve.asgi.asgi_app`):

    ```python title="app.py"
    from fastapi import FastAPI
    from ductor import Client, WorkflowOpts, Trigger
    from ductor.serve.asgi import asgi_app

    app = FastAPI()
    client = Client(app_id="checkout")
    client.workflow("place-order", WorkflowOpts(trigger=Trigger(event="order.placed")))

    app.mount("/api/ductor", asgi_app(client))
    ```

    <Callout type="warn" title="Workflow execution is unavailable in Python">
      The Python SDK supports `discover`, `health-check`, and `code`. The `execute`
      action returns **HTTP 501**, so use this SDK for registration and discovery,
      not workflow execution.
    </Callout>
  </Tab>
</Tabs>

## Operational gotchas [#operational-gotchas]

These apply regardless of language:

* **`ductor dev` still needs Postgres.** The dev harness embeds Redis (via
  miniredis) but not the database — you must provide a reachable Postgres.
* **`.env` is not auto-loaded in production.** Local dev may read it, but a
  production process expects real environment variables to be set.
* **An empty signing key disables HMAC verification.** Convenient in development,
  unacceptable in production — always configure a key for a deployed bridge.

## Related [#related]

<Cards>
  <Card title="Bridge protocol" href="/docs/sdks/bridge-protocol">
    The actions, opcodes, and HMAC scheme every adapter above implements.
  </Card>

  <Card title="CLI reference" href="/docs/reference/cli">
    The `ductor` subcommands, including `ductor dev`.
  </Card>
</Cards>
