# Get started

> Protect a form in three steps: create a site, embed the widget, and validate the token on your server.

This guide protects one HTML form. It takes about ten minutes. You need
access to the AgentGate console ([request access](/signup)) and a server
that handles the form.

In the examples, `https://agentgate.example` stands for your AgentGate
endpoint: the console shows the right value in its snippets.

> [!IMPORTANT]
> Step 3 is not optional. The widget only produces a token; your server must
> redeem that token with `POST /v1/siteverify` and refuse the request when
> it does not succeed. Without step 3 nothing is protected.

## 1. Create a site

A **site** is one website or app you protect. It has a public **site key**
(`site_…`) for your pages, one or more **backend secrets** (`ags_…`) for
your server, the **origins** allowed to use the site key, and the
**actions** a token can be issued for.

In the console at [/app](/app):

1. Choose **Add site**, enter the site address (for example
   `https://shop.example`) and a name. The site is created in
   [monitor mode](/docs/monitor-and-enforce) with one browser-checked
   action, `submit`.
2. Create a **backend secret** and copy it. It is shown once; AgentGate keeps
   only a hash. Store it in your server's secret manager or environment as
   `AGENTGATE_SECRET`, never in page code.
3. Keep the page open: its **install check** says "waiting" until AgentGate
   sees the first request for the site key, then "live".

Only the listed origins may use the site key from a browser. Add every
origin that serves the form, including staging hosts. For local development
and CI use the [testing keys](/docs/testing), which work from any origin.

## 2. Embed the widget

Add the script once per page and put the widget element inside the form.
Use your site key and the action you configured:

```html title="HTML form"
<script src="https://agentgate.example/sdk/v1/agentgate.js" async></script>

<form method="post" action="/contact">
  <!-- your fields -->
  <div class="agentgate" data-sitekey="site_…" data-action="submit"
       data-mode="managed"></div>
  <button>Send</button>
</form>
```

```js title="JavaScript"
// Load https://agentgate.example/sdk/v1/agentgate.js with a <script> tag first.
// Call init at page load, so the behaviour trace covers the whole visit.
AgentGate.init({ siteKey: "site_…", endpoint: "https://agentgate.example" });

form.addEventListener("submit", async (e) => {
  e.preventDefault();
  const { token } = await AgentGate.execute({ action: "submit" });
  await fetch("/api/contact", {
    method: "POST",
    headers: { "Content-Type": "application/json", "X-AgentGate-Token": token },
    body: JSON.stringify(Object.fromEntries(new FormData(form))),
  });
});
```

With the HTML form, the widget adds a hidden `agentgate_token` field and
fills it when the form is submitted. The verification runs at submit time,
so the token is fresh (tokens live two minutes by default). `data-mode`
chooses what people see: nothing (`invisible`, the default), a small
"Protected" status chip (`managed`), or a Verify button (`interactive`).
See [Widget modes](/docs/widget-modes).

With JavaScript, call `AgentGate.init()` at page load and
`AgentGate.execute()` at submit time, then send the token with the request
in any field or header your server reads. Calling `execute()` without
`init()` works but is treated as suspicious (the visible check is asked
for), because the behaviour trace is missing. See the
[JavaScript API](/docs/javascript-api).

If your page sets a Content Security Policy, allow the endpoint in
`script-src` and `connect-src` and add `worker-src blob:`
([details](/docs/content-security-policy)).

## 3. Validate the token

When the form arrives, send the token to `/v1/siteverify` with the backend
secret, **before** you do anything with the request:

```sh title="curl"
curl -sS https://agentgate.example/v1/siteverify \
  -H "Authorization: Bearer $AGENTGATE_SECRET" \
  -H "Content-Type: application/json" \
  -d '{"token":"PASTE_THE_TOKEN","expectedAction":"submit","expectedOrigin":"https://shop.example"}'
```

```js title="Node"
// Node 18+ (global fetch). token = the form's agentgate_token field.
async function verifyAgentGate(token, requestId) {
  const r = await fetch("https://agentgate.example/v1/siteverify", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: "Bearer " + process.env.AGENTGATE_SECRET,
    },
    body: JSON.stringify({
      token,
      expectedAction: "submit",
      expectedOrigin: "https://shop.example",
      requestId, // your ID for this operation; a retry with it is safe
    }),
  });
  const v = await r.json();
  return v.success === true; // false: do not perform the operation (v.error says why)
}
```

```python title="Python"
# pip install requests. token = the form's agentgate_token field.
import os
import requests

def verify_agentgate(token: str, request_id: str) -> bool:
    r = requests.post(
        "https://agentgate.example/v1/siteverify",
        headers={"Authorization": "Bearer " + os.environ["AGENTGATE_SECRET"]},
        json={
            "token": token,
            "expectedAction": "submit",
            "expectedOrigin": "https://shop.example",
            "requestId": request_id,
        },
        timeout=5,
    )
    return r.json().get("success") is True  # False: reject (see "error")
```

```go title="Go"
// token = r.FormValue("agentgate_token")
func verifyAgentGate(ctx context.Context, token, requestID string) (bool, error) {
	body, _ := json.Marshal(map[string]string{
		"token":          token,
		"expectedAction": "submit",
		"expectedOrigin": "https://shop.example",
		"requestId":      requestID,
	})
	req, err := http.NewRequestWithContext(ctx, http.MethodPost,
		"https://agentgate.example/v1/siteverify", bytes.NewReader(body))
	if err != nil {
		return false, err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+os.Getenv("AGENTGATE_SECRET"))
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return false, err
	}
	defer resp.Body.Close()
	var v struct {
		Success bool   `json:"success"`
		Error   string `json:"error"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&v); err != nil {
		return false, err
	}
	return v.Success, nil // false: do not perform the operation
}
```

A success looks like
`{"success":true,"decision":"allow","decisionId":"…","action":"submit","origin":"https://shop.example","issuedAt":…,"mode":"monitor"}`.
Anything else carries an `error` code such as `token_used` or
`expired_token`: do not perform the operation. Each token can be redeemed
once. The full contract, including safe retries, is in
[Validate tokens](/docs/siteverify).

## 4. Watch, then enforce

In monitor mode the widget always gets a token and `/v1/siteverify` accepts
every valid one; `wouldDecision` in the answer (and on the dashboard) shows
what enforce mode would have done. When the
decisions look right, switch the site to **enforce** in the console. See
[Monitor and enforce](/docs/monitor-and-enforce).

The site's [integration health](/docs/health-and-analytics) warns you if
tokens are issued but your server never verifies them, if pages on other
origins try to use the site key, or if siteverify calls keep failing.

## Next steps

- Using React, Vue or a mobile WebView? Read
  [Single-page apps and mobile](/docs/spa-and-mobile).
- Protect routes without a page (APIs, webhooks, agents) with the
  [gateway](/docs/gateway).
- Write end-to-end tests with the [testing keys](/docs/testing).
