Skip to content
Back to skills

076 Api 41b37b98

ASecurity

The Turnstile JavaScript API is available at `window.turnstile` after loading the script.

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 11, 2026
developmentjavascripttypescriptjavaapi

Works with

  • cli
  • api

Security analysis

A100/100

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 076-api_41b37b98 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 076 Api 41b37b98?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 076 Api 41b37b98
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-076-api-41b37b98/badge)](https://www.skillsdirectory.com/skills/tools-only-076-api-41b37b98)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
# API Reference

## Client-Side JavaScript API

The Turnstile JavaScript API is available at `window.turnstile` after loading the script.

### `turnstile.render(container, options)`

Renders a Turnstile widget into a container element.

**Parameters:**

- `container` (string | HTMLElement): CSS selector or DOM element
- `options` (TurnstileOptions): Configuration object (see [configuration.md](configuration.md))

**Returns:** `string` - Widget ID for use with other API methods

**Example:**

```javascript
const widgetId = window.turnstile.render('#my-container', {
  sitekey: 'YOUR_SITE_KEY',
  callback: (token) => console.log('Success:', token),
  'error-callback': (code) => console.error('Error:', code),
})
```

### `turnstile.reset(widgetId)`

Resets a widget (clears token, resets challenge state). Useful when form validation fails.

**Parameters:**

- `widgetId` (string): Widget ID from `render()`, or container element

**Returns:** `void`

**Example:**

```javascript
// Reset on form error
if (!validateForm()) {
  window.turnstile.reset(widgetId)
}
```

### `turnstile.remove(widgetId)`

Removes a widget from the DOM completely.

**Parameters:**

- `widgetId` (string): Widget ID from `render()`

**Returns:** `void`

**Example:**

```javascript
// Cleanup on navigation
window.turnstile.remove(widgetId)
```

### `turnstile.getResponse(widgetId)`

Gets the current token from a widget (if challenge completed).

**Parameters:**

- `widgetId` (string): Widget ID from `render()`, or container element

**Returns:** `string | undefined` - Token string, or undefined if not ready

**Example:**

```javascript
const token = window.turnstile.getResponse(widgetId)
if (token) {
  submitForm(token)
}
```

### `turnstile.isExpired(widgetId)`

Checks if a widget's token has expired (>5 minutes old).

**Parameters:**

- `widgetId` (string): Widget ID from `render()`

**Returns:** `boolean` - True if expired

**Example:**

```javascript
if (window.turnstile.isExpired(widgetId)) {
  window.turnstile.reset(widgetId)
}
```

## Callback Signatures

```typescript
type TurnstileCallback = (token: string) => void
type ErrorCallback = (errorCode: string) => void
type TimeoutCallback = () => void
type ExpiredCallback = () => void
type BeforeInteractiveCallback = () => void
type AfterInteractiveCallback = () => void
type UnsupportedCallback = () => void
```

## Siteverify API (Server-Side)

**Endpoint:** `https://challenges.cloudflare.com/turnstile/v0/siteverify`

### Request

**Method:** POST  
**Content-Type:** `application/json` or `application/x-www-form-urlencoded`

```typescript
interface SiteverifyRequest {
  secret: string // Your secret key (never expose client-side)
  response: string // Token from cf-turnstile-response
  remoteip?: string // User's IP (optional but recommended)
  idempotency_key?: string // Unique key for idempotent validation
}
```

**Example:**

```javascript
// Cloudflare Workers
const result = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    secret: env.TURNSTILE_SECRET,
    response: token,
    remoteip: request.headers.get('CF-Connecting-IP'),
  }),
})
const data = await result.json()
```

### Response

```typescript
interface SiteverifyResponse {
  success: boolean // Validation result
  challenge_ts?: string // ISO timestamp of challenge
  hostname?: string // Hostname where widget was solved
  'error-codes'?: string[] // Error codes if success=false
  action?: string // Action name from widget config
  cdata?: string // Custom data from widget config
}
```

**Example Success:**

```json
{
  "success": true,
  "challenge_ts": "2024-01-15T10:30:00Z",
  "hostname": "example.com",
  "action": "login",
  "cdata": "user123"
}
```

**Example Failure:**

```json
{
  "success": false,
  "error-codes": ["timeout-or-duplicate"]
}
```

## Error Codes

| Code                     | Cause                           | Solution                          |
| ------------------------ | ------------------------------- | --------------------------------- |
| `missing-input-secret`   | Secret key not provided         | Include `secret` in request       |
| `invalid-input-secret`   | Secret key is wrong             | Check secret key in dashboard     |
| `missing-input-response` | Token not provided              | Include `response` token          |
| `invalid-input-response` | Token is invalid/malformed      | Verify token from widget          |
| `timeout-or-duplicate`   | Token expired (>5min) or reused | Generate new token, validate once |
| `internal-error`         | Cloudflare server error         | Retry with exponential backoff    |
| `bad-request`            | Malformed request               | Check JSON/form encoding          |

## TypeScript Types

```typescript
interface TurnstileOptions {
  sitekey: string
  action?: string
  cData?: string
  callback?: (token: string) => void
  'error-callback'?: (errorCode: string) => void
  'expired-callback'?: () => void
  'timeout-callback'?: () => void
  'before-interactive-callback'?: () => void
  'after-interactive-callback'?: () => void
  'unsupported-callback'?: () => void
  theme?: 'light' | 'dark' | 'auto'
  size?: 'normal' | 'compact' | 'flexible'
  tabindex?: number
  'response-field'?: boolean
  'response-field-name'?: string
  retry?: 'auto' | 'never'
  'retry-interval'?: number
  language?: string
  execution?: 'render' | 'execute'
  appearance?: 'always' | 'execute' | 'interaction-only'
  'refresh-expired'?: 'auto' | 'manual' | 'never'
}

interface Turnstile {
  render(container: string | HTMLElement, options: TurnstileOptions): string
  reset(widgetId: string): void
  remove(widgetId: string): void
  getResponse(widgetId: string): string | undefined
  isExpired(widgetId: string): boolean
  execute(container?: string | HTMLElement, options?: TurnstileOptions): void
}

declare global {
  interface Window {
    turnstile: Turnstile
    onloadTurnstileCallback?: () => void
  }
}
```

## Script Loading

```html
<!-- Standard -->
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>

<!-- Explicit render mode -->
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit"></script>

<!-- With load callback -->
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onloadTurnstileCallback"></script>
<script>
  window.onloadTurnstileCallback = () => {
    window.turnstile.render('#container', { sitekey: 'YOUR_SITE_KEY' })
  }
</script>
```

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.