> ## Documentation Index
> Fetch the complete documentation index at: https://docs.instantcampaign.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Official SDKs (TypeScript and Python)

> Call the InstantCampaign API from TypeScript, JavaScript or Python with typed requests, typed errors, safe retries and pagination helpers.

InstantCampaign has two official SDKs. Both wrap the same public API described in the [API reference](/api-reference/introduction), use your [workspace API key](/guides/developers/api-keys), and are built from the same OpenAPI document, so a field the API documents is a field the SDK knows.

| | TypeScript / JavaScript | Python |
| - | - | - |
| Package | `@instantcampaign/sdk` | `instantcampaign` |
| Version | 0.1.0 | 0.1.0 |
| Runs on | Node.js 18+, Deno, Bun, Cloudflare Workers, Vercel and Next.js edge | Python 3.9+ |
| Dependencies | None (uses `fetch`) | `httpx` |

<Note>
  Version 0.1.0 covers every public endpoint except the MCP server, which AI assistants connect to directly ([MCP server](/guides/developers/mcp-server)). Method names may still change before 1.0.
</Note>

## Install

<Warning>
  Pre-release. Version 0.1.0 of both packages is not yet published to npm or PyPI. Until it is, install from the repository's `sdks/typescript` and `sdks/python` folders, or use the REST API directly. The commands below will work once 0.1.0 is published.
</Warning>

<CodeGroup>
  ```bash npm theme={null}
  npm install @instantcampaign/sdk
  ```

  ```bash pip theme={null}
  pip install instantcampaign
  ```
</CodeGroup>

## Send your first email

Create a key under **Settings → Developers → API Keys** with the **Transactional — send** scope, and keep it in an environment variable called `INSTANTCAMPAIGN_API_KEY`. Both SDKs read that variable when you do not pass a key.

<CodeGroup>
  ```ts TypeScript theme={null}
  import { InstantCampaign } from "@instantcampaign/sdk";

  const ic = new InstantCampaign({ apiKey: process.env.INSTANTCAMPAIGN_API_KEY });

  const message = await ic.transactional.send(
    {
      to: "jane@example.com",
      subject: "Your order {{ data.order.id }}",
      templateId: "clx…receipt",
      data: { order: { id: "10422", total: "£42.00" } },
    },
    { idempotencyKey: "order-10422" },
  );
  console.log(message.id, message.status, message.deduplicated);
  ```

  ```python Python theme={null}
  from instantcampaign import InstantCampaign

  ic = InstantCampaign()  # reads INSTANTCAMPAIGN_API_KEY

  message = ic.transactional.send(
      {
          "to": "jane@example.com",
          "subject": "Your order {{ data.order.id }}",
          "templateId": "clx…receipt",
          "data": {"order": {"id": "10422", "total": "£42.00"}},
      },
      idempotency_key="order-10422",
  )
  print(message["id"], message["status"], message.get("deduplicated"))
  ```
</CodeGroup>

## What is covered

| Resource | TypeScript | Python |
| - | - | - |
| Account | `ic.me()` | `ic.me()` |
| Contacts | `contacts.list / listAll / upsert / findByEmail / get / getByEmail / update / delete / customFields` | `contacts.list / list_all / upsert / find_by_email / get / get_by_email / update / delete / custom_fields` |
| Lists | `lists.list / create / subscribe / unsubscribe` | same, snake\_case |
| Segments, templates, campaigns | `segments.list`, `templates.list`, `campaigns.list` (read-only, like the API) | same |
| Journeys | `journeys.list / trigger / triggerWithKey` | `journeys.list / trigger / trigger_with_key` |
| Events and goals | `events.record / import` | `events.record / import_` |
| Webhooks (REST hooks) | `webhooks.events / samples / subscribe / unsubscribe` | same |
| Transactional email | `transactional.send / validate / list / listAll / status / statusByExternalId` | `transactional.send / validate / list / list_all / status / status_by_external_id` |

For an endpoint a version does not wrap yet, both clients have a low-level `request(method, path, …)` that still adds the key, the retries and the typed errors.

## Idempotency: safe retries

The API's idempotency key is the `externalId` field on [transactional sends](/guides/developers/transactional-email) and on events. A repeat with the same `externalId` returns the original result (`deduplicated: true`, or `status: "duplicate"` for events) and sends or records nothing. The SDKs' `idempotencyKey` / `idempotency_key` option fills in `externalId` for you; an `externalId` you set yourself always wins. There is no `Idempotency-Key` header.

The SDKs retry on their own only when it is safe:

* **429 Rate limit exceeded**, on any request, after the `Retry-After` the server sends (a rate-limited request was not processed).
* **5xx and network errors**, on `GET` and `DELETE` only.

A `POST` that fails with a 5xx is never retried for you. Send it again with the same `externalId`. Set `maxRetries` (TypeScript) or `max_retries` (Python) to change the number of retries; the default is 2.

## Errors

Every error response becomes a typed error with `status`, the API's machine-readable `code`, the response body and the request id to quote to support.

| HTTP | TypeScript | Python |
| - | - | - |
| 400, 422 | `ValidationError` (`fields`) | `ValidationError` (`fields`) |
| 401 | `AuthenticationError` | `AuthenticationError` |
| 402 | `QuotaExceededError` | `QuotaExceededError` |
| 403 `insufficient_scope` | `InsufficientScopeError` (`requiredScope`) | `InsufficientScopeError` (`required_scope`) |
| other 403 | `PermissionDeniedError` | `PermissionDeniedError` |
| 404 | `NotFoundError` | `NotFoundError` |
| 409 | `ConflictError` | `ConflictError` |
| 429 | `RateLimitError` (`retryAfterSeconds`) | `RateLimitError` (`retry_after`) |
| 5xx | `ServerError` | `ServerError` |
| no answer | `InstantCampaignConnectionError` | `InstantCampaignConnectionError` |

<CodeGroup>
  ```ts TypeScript theme={null}
  import { InsufficientScopeError } from "@instantcampaign/sdk";

  try {
    await ic.events.record({ event: "order.completed", email: "jane@example.com", externalId: "order-10422" });
  } catch (err) {
    if (err instanceof InsufficientScopeError) {
      console.error(`This key needs the ${err.requiredScope ?? "events:write"} scope`);
    } else throw err;
  }
  ```

  ```python Python theme={null}
  from instantcampaign import InsufficientScopeError

  try:
      ic.events.record({"event": "order.completed", "email": "jane@example.com", "externalId": "order-10422"})
  except InsufficientScopeError as err:
      print(f"This key needs the {err.required_scope or 'events:write'} scope")
  ```
</CodeGroup>

A scoped key that lacks a scope gets `InsufficientScopeError`. Add the scope to the key from **Settings → Developers → API Keys** (pencil icon → **Edit key**); the change applies on the key's next request.

## Pagination

Contacts are paged by number and transactional messages by a `before` cursor. The `listAll` / `list_all` helpers walk every page for you:

<CodeGroup>
  ```ts TypeScript theme={null}
  for await (const message of ic.transactional.listAll({ status: "bounced" })) {
    console.log(message.to, message.errorMessage);
  }
  ```

  ```python Python theme={null}
  for contact in ic.contacts.list_all(list_id="clx…newsletter"):
      print(contact["email"])
  ```
</CodeGroup>

## Options

| TypeScript | Python | Default | What it does |
| - | - | - | - |
| `apiKey` | `api_key` | `INSTANTCAMPAIGN_API_KEY` | Workspace API key (`ic_…`) or OAuth access token (`ica_…`) |
| `baseUrl` | `base_url` | `https://instantcampaign.ai/api` | API origin, for a staging host |
| `timeoutMs` | `timeout` | 30 s | Per-attempt timeout |
| `maxRetries` | `max_retries` | 2 | Retries for 429, and for 5xx/network errors on GET and DELETE |
| `fetch` | `transport` | built in | Bring your own `fetch` or HTTP transport (proxies, tests) |

<Warning>
  Never put a workspace API key in code that runs in a browser. For events from a website, use a [tracking token](/guides/audience-and-campaigns/events-and-goals) and `track.js` instead.
</Warning>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.