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

# Node.js

> Headless server-side submission with an API key.

```bash theme={null}
npm install @raykoi/sdk
```

Headless — no built-in DOM injection, since you render your own UI. An API key skips CAPTCHA entirely.

```javascript theme={null}
import { createClient } from '@raykoi/sdk';

const client = createClient({ apiKey: 'rk_live_YOUR_SECRET_KEY' });
const form = client.form('YOUR_PUBLIC_ID');

try {
  await form.submit({
    email: 'user@example.com',
    name: 'Alice',
  });
} catch (err) {
  if (err.type === 'validation_error') {
    // err.errors: [{ field, message, type }]
  }
}
```

`client.form(id)` scopes a client to one form so you can call `.submit()` (and `.validate()`, `.upload()`) on it repeatedly — `client.forms.submit(id, data)` is an equivalent one-off shorthand for the same thing.

Not using Node.js specifically? The same `createClient()` / `client.form(id).submit()` pattern works identically in [Solid.js, Alpine.js, and jQuery](/integrations/other-frontend) — or use the raw HTTP contract directly in [any backend language](/integrations/backend-languages).

## CAPTCHA from Node.js

`client.form(id).submit()` never mounts or manages a CAPTCHA widget — that's true from Node.js and equally true calling the same method from a browser (see [Other Frontend Options](/integrations/other-frontend#captcha) for the browser case). The framework adapters (`useRaykoiForm` etc.) and `submit.js` are the two paths that own a widget's lifecycle for you; the raw client, on any runtime, always resolves one of two ways:

* **Authenticating with an API key** (as above) — the normal case. A secret key authenticates the request as server-to-server and **skips the CAPTCHA gate entirely**, same as it does for the [Server Data API](/dashboard/api-keys#using-it). Nothing to configure.
* **No API key** — you must acquire and pass `captchaToken` yourself. In Node there's no DOM to render a widget in at all, so this only makes sense when something else produced the token (a proxied browser submission, a headless-browser test harness, etc.):

```javascript theme={null}
await form.submit(data, { captchaToken: tokenAcquiredSomehow });
```

This applies whether the form uses a self-managed provider (Turnstile/hCaptcha/reCAPTCHA) or [Managed CAPTCHA](/essentials/pro-features/managed-captcha) — from the SDK's side they're indistinguishable, so the same two options above cover both.
