---
seo_title: Integrations: MCP, n8n, CLI, SDKs and Widgets | EnConvert
meta_desc: Reach the EnConvert API from the tools you already use: an MCP server for coding agents, an n8n node, a terminal CLI, ten SDKs, and embeddable web widgets.
keywords: enconvert integrations, mcp server file conversion, n8n file conversion node, file conversion cli, file conversion sdk, embed file converter widget website, website file conversion widget, no-code file conversion widget, iframe file converter widget, wordpress file converter shortcode, url to pdf widget embed
---

# Integrations

The same API, reachable from the tools you already use. An MCP server drops EnConvert into a coding agent, an n8n node into a workflow, a CLI into your terminal, ten SDKs into your application code, and a web widget onto your own website.

---

## Pick your surface

Every surface below calls the same public REST endpoints with the same API key, against the same project and the same monthly operations allowance.

| Surface | Package | Use it when |
|---------|---------|-------------|
| [MCP Setup](/docs/guides/integrations/mcp-setup.md) | `@enconvert/mcp` | A coding agent such as Claude Code, Cursor, Windsurf, or Claude Desktop should call the API itself, in chat, without you writing HTTP. |
| [n8n](/docs/guides/integrations/n8n.md) | `@enconvert/n8n-nodes-enconvert` | You are building an n8n workflow and want conversions, scraping and crawling as a node that emits real binary data. |
| [CLI](/docs/guides/integrations/cli.md) | `@enconvert/cli` | You want to convert files or pull web data from a terminal or a shell script, with `--json` output and stable exit codes. |
| [SDKs](/docs/guides/integrations/sdks.md) | ten language clients | You are writing application code and want typed methods with editor autocomplete instead of hand-rolled HTTP. |

There is a fifth surface with no page of its own: [web widgets](#web-widgets), covered below, which is the only one your end users touch directly.

If you are still deciding, [REST, MCP and CLI](/docs/concepts/rest-mcp-and-cli.md) compares the access surfaces side by side, and [Authentication](/docs/authentication.md) explains which key type each one needs.

---

## Web widgets {: #web-widgets }

Web widgets embed URL and file conversion into any website with a single script tag, so your visitors convert a URL to a PDF, capture a screenshot, or convert an uploaded file without leaving your page. The embed code carries only a widget ID. Authentication happens inside the iframe through a Cloudflare Turnstile challenge and a short-lived JWT from `POST /v1/widget/{widget_id}/token`, so no API key is ever exposed in your frontend code.

Each widget is tied to one conversion endpoint and one list of allowed domains, both set in the dashboard. Any conversion endpoint can back a widget:

- URL-based: [url-to-pdf](/docs/endpoints/convert/web-pages/url-to-pdf.md), [url-to-screenshot](/docs/endpoints/convert/web-pages/url-to-screenshot.md)
- File-based: All [data format](/docs/endpoints/convert/data-formats.md), [document to PDF](/docs/endpoints/convert/documents.md), and [image conversion](/docs/endpoints/convert/images.md) endpoints

### How Widgets Work

#### Setup

1. Go to your EnConvert **Dashboard > Widgets** and click **Create Widget**.
2. Select the conversion endpoint (e.g., `/v1/convert/url-to-pdf`) and specify the domains where the widget will be embedded. Wildcard subdomains are supported (e.g., `*.example.com`).
3. An internal public API key is automatically created for the widget, restricted to the selected endpoint and allowed domains. This key is never exposed.

Two preconditions catch people out: your account email must be verified, and the allowed-domains list cannot be empty. Widget creation is rejected if either is missing.

#### Embedding

Add the embed script to your website:

```html
<script src="https://enconvert.com/embed.js" data-widget-id="your-widget-id"></script>
```

The script creates a sandboxed iframe that loads the EnConvert widget. No API key appears in the embed code.

#### Runtime Flow

1. **Widget loads** in the iframe and fetches its configuration from `GET /v1/widget/{widget_id}/config`.
2. **Domain validation**: the widget verifies the parent page's origin matches the allowed domains list. Supports exact domains and wildcard subdomain patterns (`*.example.com`).
3. **User submits a URL or file**: the widget requests an invisible Turnstile challenge token.
4. **Token exchange**: the widget sends the Turnstile token to `POST /v1/widget/{widget_id}/token` and receives a JWT (1-hour expiry) plus a refresh token cookie (7-day expiry).
5. **Conversion**: the widget calls the conversion endpoint with the JWT.
6. **Result**: the API returns a JSON response with a `presigned_url`. The widget displays a download link.
7. **Timeout recovery**: if the conversion exceeds reverse-proxy timeout limits, the widget polls `GET /v1/convert/status/{job_id}` using the pre-generated job ID.

#### Automatic Token Refresh

The widget never stops working due to expired authentication:

- On the initial conversion, the API issues both a **JWT** (1-hour expiry) and a **refresh token** (7-day expiry, httpOnly cookie).
- On subsequent conversions, the widget first attempts to **refresh the JWT** via `POST /v1/widget/{widget_id}/refresh` using the refresh token cookie, with no Turnstile challenge required.
- If the refresh token itself has expired (after 7 days of inactivity), the widget falls back to a new Turnstile challenge.
- The refresh token is **rotated** on every refresh: each refresh issues a new 7-day cookie.

This means a widget visitor who converts every few days will never see a Turnstile challenge after the first one.

### Widget Endpoints

#### Configuration

Retrieves the configuration for a specific widget. No authentication required.

```
GET /v1/widget/{widget_id}/config
```

**Response:**

```json
{
    "endpoint": "/v1/convert/url-to-pdf",
    "input_type": "url",
    "allowed_domains": ["https://example.com", "*.example.com"],
    "turnstile_site_key": "1x00000000000000000000AA",
    "widget_branding": true
}
```

| Field | Description |
|-------|-------------|
| `endpoint` | The conversion endpoint this widget is configured to use. |
| `input_type` | `"url"` for URL-based endpoints, `"file"` for file upload endpoints. |
| `allowed_domains` | Domains authorized to embed this widget. Supports wildcards. |
| `turnstile_site_key` | Cloudflare Turnstile site key for bot verification. |
| `widget_branding` | Whether the "Powered by EnConvert" badge is displayed. Determined by the subscription plan. |

#### Token Exchange

Exchanges a Turnstile challenge token for a JWT. Sets a refresh token cookie.

```
POST /v1/widget/{widget_id}/token
X-Parent-Origin: https://your-website.com
Content-Type: application/json

{
    "turnstile_token": "cloudflare-challenge-response-token"
}
```

**Response:**

```json
{
    "token": "eyJhbGciOiJIUzI1NiIs...",
    "token_type": "Bearer",
    "expires_in": 3600
}
```

Also sets an httpOnly `refresh_token` cookie (7-day expiry, `Secure`, `SameSite=none`).

#### Token Refresh

Refreshes an expired JWT using the httpOnly refresh token cookie. No Turnstile challenge required.

```
POST /v1/widget/{widget_id}/refresh
X-Parent-Origin: https://your-website.com
```

No request body needed. The refresh token is read from the cookie automatically.

**Response:**

```json
{
    "token": "eyJhbGciOiJIUzI1NiIs...",
    "token_type": "Bearer",
    "expires_in": 3600
}
```

The refresh token cookie is rotated on each refresh (new 7-day cookie issued).

**Error responses:**
- `401`: no refresh token cookie or refresh token expired
- `403`: refresh token does not match the widget's project, or domain not authorized
- `404`: widget not found or deactivated

### Conversion Response

Both URL-based and file-based widget conversions return a consistent JSON response with a presigned download URL:

```json
{
    "presigned_url": "https://spaces.example.com/...",
    "object_key": "env/files/{project_id}/url-to-pdf/example_20260405_123456789.pdf",
    "filename": "example_20260405_123456789.pdf",
    "file_size": 123456,
    "conversion_time_seconds": 8.5,
    "job_id": "client-generated-uuid"
}
```

The widget uses the `presigned_url` to display a download link. Presigned URLs expire after 15 minutes. See [Signed URLs](/docs/concepts/signed-urls.md) for what that window means for your visitors.

**Widget conversion restrictions:**
- Single URL / single file only
- Synchronous mode only (no async or batch)
- No webhook callbacks or notification emails
- Endpoint restricted to the one configured for the widget

### Widget Branding

Plans that include widget branding display a small **"Powered by EnConvert"** badge at the bottom of the widget. This is controlled by the `widget_branding` field on the subscription plan:

| Plan | Branding |
|------|----------|
| Founding (free) | Displayed |
| Indie, Studio, Production, Enterprise | Hidden |

The branding badge links to `https://www.enconvert.com` and is styled to be unobtrusive: small text below the widget form with reduced opacity.

### Widget Management

Widgets are managed through the EnConvert dashboard or the backend API:

| Operation | Endpoint | Description |
|-----------|----------|-------------|
| Create | `POST /widgets` | Creates a widget and auto-generates an internal public API key. |
| List | `GET /widgets?project_id={id}` | Lists all active widgets for a project. |
| Get | `GET /widgets/{id}` | Retrieves a single widget's details. |
| Update | `PATCH /widgets/{id}` | Updates widget name, endpoint, or API key. |
| Delete | `DELETE /widgets/{id}` | Soft-deletes the widget (sets `active=false`). |

<div class="alert alert-info">
<strong>Different host:</strong> these management routes live on the EnConvert backend that serves the dashboard, not on <code>api.enconvert.com</code>. The conversion and widget-auth routes under <code>/v1/</code> are the gateway.
</div>

### Embed Code Reference

#### Standard HTML

```html
<script src="https://enconvert.com/embed.js" data-widget-id="your-widget-id"></script>
```

The script:
- Creates a sandboxed iframe (`allow-scripts allow-same-origin allow-forms allow-popups`)
- Sets `width: 100%`, initial height 400px, no border
- Enables `clipboard-write` permission
- Uses lazy loading
- Listens for `Enconvert:resize` messages to auto-adjust height

#### WordPress Shortcode

If you use the EnConvert WordPress plugin, embed widgets using the shortcode:

```
[enconvert_widget id="your-widget-id"]
```

#### Style Customization

Customize the widget appearance via query parameters on the embed script URL or the iframe source:

| Parameter | CSS Variable | Description |
|-----------|-------------|-------------|
| `bg` | `--w-bg` | Widget background color |
| `text` | `--w-text` | Text color |
| `btn-bg` | `--w-btn-bg` | Button background color |
| `btn-text` | `--w-btn-text` | Button text color |
| `border` | `--w-border` | Border color |
| `radius` | `--w-radius` | Border radius |
| `input-bg` | `--w-input-bg` | Input field background |
| `result-bg` | `--w-result-bg` | Result area background |
| `error` | `--w-error` | Error text color |
| `font` | `--w-font` | Font family |
| `padding` | `--w-padding` | Widget padding |
| `max-width` | `--w-max-width` | Maximum widget width |

### Iframe Communication

The widget communicates with the parent page via `postMessage`. Listen for these events on the parent page:

| Event Type | Data | Description |
|-----------|------|-------------|
| `Enconvert:ready` | none | Widget has loaded and is ready. |
| `Enconvert:resize` | `{ height: number }` | Widget content height changed. Use to resize the iframe. |
| `Enconvert:conversion:complete` | `{ url: string, filename?: string }` | Conversion completed. `url` is the presigned download URL. |
| `Enconvert:conversion:error` | `{ error: string }` | Conversion failed. |

These four event names are a frozen wire contract. Match the casing exactly.

#### Example: Listening for Events

```javascript
window.addEventListener("message", function(e) {
    if (!e.data || !e.data.type) return;

    if (e.data.type === "Enconvert:conversion:complete") {
        console.log("Conversion done:", e.data.data.url);
    }

    if (e.data.type === "Enconvert:conversion:error") {
        console.error("Conversion failed:", e.data.data.error);
    }
});
```

### Security

| Layer | Protection |
|-------|-----------|
| **Domain whitelisting** | Widget only functions on listed domains. Supports exact matches and wildcard subdomains. Server-side validation on token issuance. |
| **Turnstile verification** | Every initial token request requires a valid Cloudflare Turnstile challenge response. |
| **Endpoint restriction** | Each widget is locked to a single conversion endpoint via `allowed_endpoints` in the JWT. |
| **Token expiry** | JWT expires after 1 hour. Refresh token expires after 7 days. Both are rotated on refresh. |
| **Refresh token security** | httpOnly cookie with `Secure` and `SameSite=none`, inaccessible to JavaScript, only sent over HTTPS. |
| **CORS protection** | API gateway validates the widget iframe origin on every request. |
| **CSP frame-ancestors** | Widget config and token endpoints set `frame-ancestors` headers restricting which domains can embed the iframe. |
| **No exposed keys** | Embed code contains only the widget ID. The internal API key is never visible. |

<div class="alert alert-warning">
<strong>Never put a private key in a page.</strong> Widgets exist so that browser traffic runs on a scoped public key and a short-lived JWT. A private <code>sk_</code> key sent from a browser is rejected with HTTP 403 on sight. See <a href="/docs/authentication#public-keys-and-jwt">Public keys and JWT</a>.
</div>

---

## Frequently asked questions

### How do I embed a file converter widget on my website?

Create a widget in the EnConvert dashboard (**Dashboard > Widgets > Create Widget**), then add one script tag to your page: `<script src="https://enconvert.com/embed.js" data-widget-id="your-widget-id"></script>`. If your site runs WordPress, you can use the `[enconvert_widget id="your-widget-id"]` shortcode from the EnConvert WordPress plugin instead.

### Do I need to expose an API key to embed a conversion widget?

No. The embed code contains only the widget ID. An internal public API key is auto-generated for each widget, restricted to its configured endpoint and allowed domains, and is never visible in your frontend code.

### How does the widget authenticate users without an API key?

The widget requests an invisible Cloudflare Turnstile challenge and exchanges it at `POST /v1/widget/{widget_id}/token` for a JWT with 1-hour expiry plus an httpOnly refresh token cookie with 7-day expiry. Subsequent conversions refresh the JWT via `POST /v1/widget/{widget_id}/refresh` with no new challenge, and the refresh token is rotated on every refresh.

### Can I restrict which domains can use my embedded widget?

Yes. Each widget has an allowed-domains list that supports exact domains and wildcard subdomains like `*.example.com`, validated server-side on token issuance, with `frame-ancestors` CSP headers restricting which pages can embed the iframe.

### How do I remove the Powered by EnConvert badge from the widget?

The badge is controlled by the `widget_branding` field on your subscription plan. Only the free Founding plan shows it. Indie, Studio, Production and Enterprise all hide it, so any paid plan removes the badge.

### Which integration should I start with?

If you are writing code, start with an [SDK](/docs/guides/integrations/sdks.md) for your language. If you are automating without code, use [n8n](/docs/guides/integrations/n8n.md). If you want an AI assistant to do the work, install the [MCP server](/docs/guides/integrations/mcp-setup.md). If you just want your own visitors to convert files, use a [web widget](#web-widgets).

### Do the integrations share one API key and one quota?

Yes. All of them authenticate as the same project, so operations count against a single monthly allowance no matter which surface issued the call. See [Rate Limits and Quotas](/docs/reference/rate-limits.md).
