Sites API
REST API reference for creating, listing, and managing NeoCaptcha sites.
Updated 4/12/2026
Site Management API
The Site Management API lets you programmatically create and manage NeoCaptcha sites, rotate secrets, and control site lifecycle. All endpoints require authentication.
Getting an API key
API keys are created from the API Keys page in your dashboard (Dashboard → API Keys). Each key is scoped to a single account and is shown only once at creation time — store it in a secure secret manager immediately.
API keys start with nct_ and are never sent to the browser; they are for server-to-server use only.
Authentication
Every request to the Site Management API must include your API key as a Bearer token in the Authorization header:
Authorization: Bearer nct_your_api_key_here
Requests without a valid key receive a 401 Unauthorized response. Keys that have been revoked or have expired also return 401.
Note: API keys are for site management only. Captcha verification (
POST /api/v1/verify) uses your site secret, not an API key.
Endpoints
Create a site
POST /api/v1/sites
Creates a new site with a generated siteKey / siteSecret pair and registers its allowed origins.
Important: The
siteSecretis only returned once in this response. Store it immediately in a secure secret manager.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Human-readable name for the site |
allowedOrigins | string[] | Yes | Non-empty list of allowed origins (e.g. ["https://app.example.com"]) |
accountId | string | Yes | UUID of the account to associate this site with |
Response — 201 Created
{
"siteKey": "sk_live_abc123",
"siteSecret": "ss_live_xyz789",
"name": "My App",
"allowedOrigins": ["https://app.example.com"],
"isActive": true,
"createdAt": "2026-04-12T00:00:00Z",
"message": "Site created successfully. Save the siteSecret - it cannot be retrieved again."
}Example
const res = await fetch('/api/v1/sites', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'My App',
allowedOrigins: ['https://app.example.com'],
accountId: 'your-account-uuid',
}),
});
const { siteKey, siteSecret } = await res.json();
// Store siteSecret securely — it will not be shown againList sites
GET /api/v1/sites?accountId={accountId}
Returns all sites belonging to the specified account, including their allowed origins.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
accountId | string | Yes | UUID of the account |
Response — 200 OK
{
"sites": [
{
"site_key": "sk_live_abc123",
"name": "My App",
"is_active": true,
"created_at": "2026-04-12T00:00:00Z",
"updated_at": "2026-04-12T00:00:00Z",
"allowed_origins": ["https://app.example.com"]
}
]
}Example
const res = await fetch(`/api/v1/sites?accountId=your-account-uuid`);
const { sites } = await res.json();Deactivate a site
DELETE /api/v1/sites/{siteKey}
Soft-deletes a site by setting is_active to false. The site record is retained; active verification for this site will stop.
Path parameters
| Parameter | Type | Description |
|---|---|---|
siteKey | string | The siteKey of the site to deactivate |
Response — 200 OK
{ "success": true }Example
const res = await fetch(`/api/v1/sites/sk_live_abc123`, {
method: 'DELETE',
});Rotate site secret
POST /api/v1/sites/{siteKey}/rotate-secret
Generates a new siteSecret for the given site and invalidates the previous one. The new secret is only shown once in this response.
Important: Update your backend environment variables immediately after rotating. The old secret stops working as soon as this request succeeds.
Path parameters
| Parameter | Type | Description |
|---|---|---|
siteKey | string | The siteKey of the site whose secret should be rotated |
Response — 200 OK
{
"siteKey": "sk_live_abc123",
"newSiteSecret": "ss_live_newxyz",
"name": "My App",
"updatedAt": "2026-04-12T00:00:00Z",
"message": "Site secret rotated successfully. Save the new siteSecret - it cannot be retrieved again. Update your backend immediately."
}Example
const res = await fetch(`/api/v1/sites/sk_live_abc123/rotate-secret`, {
method: 'POST',
});
const { newSiteSecret } = await res.json();
// Store newSiteSecret securely and update your backend environmentError responses
All endpoints return a consistent error shape:
{ "error": "Description of what went wrong" }| Status | Meaning |
|---|---|
400 | Missing or invalid request parameters |
401 | Not authenticated |
403 | Authenticated but not authorized for this resource |
404 | Account or site not found |
500 | Internal server error |
Security checklist
-
siteSecretstored in a backend secret manager — never committed to source control or exposed to the browser. - Rotate secrets immediately if a leak is suspected.
- Deactivate sites that are no longer in use.
- Restrict which team members have access to the account in your account settings.