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

# Svelte

> Identify a visitor, send a Request ID with your application action and verify the result on your server.

Identify a visitor, send a Request ID with your application action and verify the result on your server. This guide uses the supported ShieldLabs package API, not a standalone generated client.

## Before you start

An existing Svelte 4 or 5 application. The complete SvelteKit example below uses Svelte 5 and separates public and private environment variables.

Register and verify the website domain in your [analytics dashboard](https://app.shieldlabs.ai/). Use credentials from the same domain and environment as the browser check. For a fresh account, start with [Quick Start](/quickstart).

## Get your credentials

Copy the Public Key for your domain from **Integration > API keys**. A public frontend environment variable may contain this key, but never a Private API Key or webhook signing secret.

## Install the SDK

```bash theme={null}
npm install @shieldlabs-ai/svelte @shieldlabs-ai/js
```

Use `yarn add` or `pnpm add` with the same package names if your project uses that package manager. Framework packages remain the application's responsibility.

## Add the integration

A SvelteKit signup form. Add your Public Key and your Private API Key to `.env`:

```bash theme={null}
PUBLIC_SHIELDLABS_PUBLIC_KEY=0123456789abcdef0123456789abcdef
SHIELDLABS_API_KEY=sec_your_private_key
```

```svelte theme={null}
<!-- src/routes/+layout.svelte -->
<script lang="ts">
  import { PUBLIC_SHIELDLABS_PUBLIC_KEY } from '$env/static/public';
  import { setShieldLabs } from '@shieldlabs-ai/svelte';

  let { children } = $props();

  // Once for the whole app. The agent loads in the browser after hydration.
  setShieldLabs({ publicKey: PUBLIC_SHIELDLABS_PUBLIC_KEY });
</script>

{@render children()}
```

```svelte theme={null}
<!-- src/routes/signup/+page.svelte -->
<script lang="ts">
  import { enhance } from '$app/forms';
  import { useIdentify } from '@shieldlabs-ai/svelte';
  import type { SubmitFunction } from '@sveltejs/kit';

  const { identify, isLoading } = useIdentify();

  const protect: SubmitFunction = async ({ formData }) => {
    const result = await identify(); // null when no identification was possible
    formData.set('requestId', result?.requestId ?? '');
  };
</script>

<form method="POST" use:enhance={protect}>
  <input name="email" type="email" required />
  <button disabled={$isLoading}>Sign up</button>
</form>
```

```ts theme={null}
// src/routes/signup/+page.server.ts
import { fail } from '@sveltejs/kit';
import { SHIELDLABS_API_KEY } from '$env/static/private';
import { ShieldLabs, ValidationError, evaluateIdentification, type Identification } from '@shieldlabs-ai/node';
import type { Actions } from './$types';

const shieldlabs = new ShieldLabs({ apiKey: SHIELDLABS_API_KEY });
const usedRequestIds = new Set<string>(); // in production, your database

export const actions = {
  default: async ({ request }) => {
    const data = await request.formData();
    const requestId = String(data.get('requestId') ?? '');
    let identification: Identification | null = null;
    if (requestId) {
      try {
        // Waits until the identification is scored (about 1-3 seconds after identify()).
        identification = await shieldlabs.identifications.get(requestId);
      } catch (error) {
        if (!(error instanceof ValidationError)) throw error; // a malformed ID stays unverified
      }
    }
    const verdict = evaluateIdentification(identification, {
      isReplay: (id) => usedRequestIds.has(id), // request IDs that already authorized an action
    });
    if (!verdict.ok) return fail(403, { message: 'We could not verify this signup.' });
    usedRequestIds.add(requestId); // one identification authorizes one signup
    // ...create the account
  },
} satisfies Actions;
```

`use:enhance` sends the form with `fetch`, so the page stays alive while the agent posts the
identification (see [Keep the page alive](https://github.com/ShieldLabs-ai/shieldlabs-js#quick-start)).
Without JavaScript, or when the agent cannot load, the form is still sent without a `requestId`,
and your server treats the signup as unverified. A runnable version is in
[`examples/sveltekit`](https://github.com/ShieldLabs-ai/shieldlabs-svelte/tree/6f93740526cc7846be823389c406dac729bcde0b/examples/sveltekit).

The History row that `identifications.get()` waits for appears about 1-3 seconds after `identify()`
resolves, and it can be refined for up to about 10 seconds as follow-up checks finish. To have the
verdict ready by the time the user submits, start the identification when the user begins the
action (see [Start when the user begins the action ](https://github.com/ShieldLabs-ai/shieldlabs-svelte/blob/6f93740526cc7846be823389c406dac729bcde0b/README.md#start-when-the-user-begins-the-action)).

> **Test on a registered domain.** ShieldLabs records identifications only for the domains
> registered in your account. On `localhost` the page still receives a `requestId`, but the
> identification is rejected with `401`, so your backend never finds it. Test on a development
> domain with its own keys, as described in [Environments](https://docs.shieldlabs.ai/setup/environments).

## Test the complete flow

1. Run the application on the registered HTTPS domain with its Public Key.
2. Trigger the protected form once and check that a Request ID is sent to your own backend.
3. Retrieve that same ID using a server SDK or locate it in the analytics dashboard.
4. Test a missing ID and an agent load failure: your backend must treat the action as unverified.

The `/api/signup` routes shown in examples belong to your application; they are not ShieldLabs API endpoints. Connect one of the [server quick starts](/api/sdks#server-packages) before testing the full action. A successful browser call does not prove scoring is complete.

## Troubleshooting

* No History row: confirm the registered domain, credential/environment match and that the browser remained open while collectors posted.
* Missing or pending verdict: scoring is asynchronous. The server helper waits within a bounded budget; handle a missing result and API errors explicitly.
* Authentication error: use the Private API Key for History, not a Public Key, Management Secret Key or MCP OAuth token.
* Invalid webhook signature: verify the original raw bytes with the endpoint's full signing secret, before trusting parsed JSON.

## Next steps

* [Runnable example](https://github.com/ShieldLabs-ai/shieldlabs-svelte/tree/6f93740526cc7846be823389c406dac729bcde0b/examples/sveltekit)
* [SDK reference and changelog](https://github.com/ShieldLabs-ai/shieldlabs-svelte)
* [Identification flow](/api/identification-flow)
* [Server API](/api/server-api)
* [Webhook setup](/setup/webhooks)
* [Content Security Policy](/setup/csp)


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