Skip to content
Coritan Docs

Manage widgets with the API

List, add, change, rotate and delete Wicket widgets, and read their numbers, with the Client API.

View as Markdown

Use these routes to manage your Wicket widgets from a script: add a widget when you deploy a site, keep its hostnames current, rotate its secret key, and read how it does. Every route is under https://api.coritan.com/api/v1/client/wicket.

  • You need an access token or an account API key (Authentication). The examples send it as $CORITAN_TOKEN.
  • Reading needs any role on the account. Adding, changing and deleting widgets and rotating keys need the account owner, or a team member with the Admin or Technical role (Team members). An API key with only Read permissions can read and cannot change anything (Authentication).
  • A widget that belongs to another account answers 404, the same as one that does not exist.
  • The account may make 60 changes in 10 minutes, counting every add, change, delete and rotation. The next one answers 429 with a Retry-After header.
Shell
curl "https://api.coritan.com/api/v1/client/wicket/widgets?status=attention&sort=-challenges" \
  -H "Authorization: Bearer $CORITAN_TOKEN"

All four query words are optional.

q
Only widgets whose name, site key or one of whose hostnames contains this text, ignoring case.
status
all (the default), active, disabled (our team turned it off) or attention (the widget needs attention).
sort
name, created, challenges or solve_rate, each with a leading - for descending. The default is -created, newest first. challenges and solve_rate use the last 24 hours, and a widget with no challenges has no solve rate and comes last.
page, per_page
The page, starting at 1, and how many widgets it holds. The default is 24 and the most is 100.

A status or sort word that is not in the lists answers 422, and the message names the words that are.

JSON
{
  "items": [
    {
      "id": 12,
      "name": "Sign-up form",
      "site_key": "wk_6fJ2kQ9xT1mZ8bP4rL0aVc",
      "mode": "managed",
      "status": "active",
      "branded": true,
      "hostnames": ["example.com", "app.example.com"],
      "hostname_count": 2,
      "created_at": "2026-10-17T15:14:30Z",
      "last_challenge_at": "2026-10-17T16:02:00Z",
      "stats_24h": {"issued": 1204, "passed": 1180, "failed": 9, "solve_rate": 0.98},
      "attention": ["not_verifying"],
      "disabled_reason": null
    }
  ],
  "total": 1,
  "page": 1,
  "per_page": 24,
  "limit": {"used": 1, "limit": 25},
  "stats": {
    "issued_24h": 1204,
    "passed_24h": 1180,
    "solve_rate_24h": 0.98,
    "attention": [{"widget_id": 12, "name": "Sign-up form", "reason": "not_verifying", "disabled_reason": null}]
  }
}
Field Meaning
hostnames, hostname_count The first three hostnames, and how many the widget lists.
stats_24h Challenges started, passed and failed in the last 24 hours. solve_rate is passed divided by issued, or null when nothing was issued.
attention Why the widget needs attention, if it does: disabled, not_verifying or hostname_refused (Analytics and what needs attention).
disabled_reason Why our team turned the widget off, for a widget whose status is disabled, and null for any other.
total How many widgets match q and status, over every page.
limit How many widgets the account has and how many its plan allows. -1 is unlimited.
stats Totals for the whole account, not only this page. attention has one entry for each reason of each widget that needs attention. Each entry has widget_id, name, reason and disabled_reason, which is the reason our team gave when reason is disabled and null for any other reason.
Shell
curl -X POST https://api.coritan.com/api/v1/client/wicket/widgets \
  -H "Authorization: Bearer $CORITAN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Sign-up form", "hostnames": ["example.com", "app.example.com"], "mode": "managed"}'

name is 1 to 100 characters, and hostnames lists at least one. mode is managed (the default), non_interactive or invisible. You can also send appearance, with any of theme (auto, light or dark), size (normal, flexible or compact) and language (auto, or a code such as es), and branded: false to leave Wicket's name off the widget.

The answer is 201. It holds the widget and its secret key:

JSON
{
  "widget": {
    "id": 12,
    "name": "Sign-up form",
    "site_key": "wk_6fJ2kQ9xT1mZ8bP4rL0aVc",
    "mode": "managed",
    "status": "active",
    "branded": true,
    "hostnames": ["example.com", "app.example.com"],
    "hostname_count": 2,
    "created_at": "2026-10-17T15:14:30Z",
    "last_challenge_at": null,
    "stats_24h": {"issued": 0, "passed": 0, "failed": 0, "solve_rate": null},
    "attention": [],
    "appearance": {"theme": "auto", "size": "normal", "language": "auto"},
    "secret_prefix": "wks_Qm4x7K",
    "secret_rotated_at": null,
    "previous_secret_expires_at": null,
    "disabled_reason": null,
    "limits": {"hostnames": 10},
    "embed": {
      "script_url": "https://wicket.coritan.com/v1/api.js",
      "siteverify_url": "https://api.coritan.com/api/v1/wicket/siteverify"
    }
  },
  "secret": "wks_Qm4x7KpR2dVbT9sLhW1cYe6NzA0uJfG3oXiBk5aMvH"
}

Important

The secret is shown once. Store it in your server's settings now. No route returns it again, and secret_prefix is only its first ten characters.

embed holds the two addresses you need: script_url is the script your page loads, and siteverify_url is where your server sends the token and the secret key.

Each hostname is checked and written in lower case, so Example.COM. is stored as example.com, and a repeated hostname is stored once. A hostname Wicket refuses answers 422 with the hostname and the reason:

JSON
{"detail": "\"exa mple.com\" is not a hostname: a hostname has no spaces"}
Shell
curl https://api.coritan.com/api/v1/client/wicket/widgets/12 \
  -H "Authorization: Bearer $CORITAN_TOKEN"

The answer has the fields of the widget above, with every hostname in hostnames. limits.hostnames is how many hostnames your plan lets one widget list. While a rotation's grace period is running, previous_secret_expires_at says when the old secret key stops working. For a widget our team turned off, status is disabled and disabled_reason says why. The list and the summary give the same disabled_reason, so you can show it without reading each widget.

Send only what changes. hostnames is the complete new list, so it replaces the current one, and a setting you leave out of appearance keeps its value.

Shell
curl -X PATCH https://api.coritan.com/api/v1/client/wicket/widgets/12 \
  -H "Authorization: Bearer $CORITAN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"hostnames": ["example.com", "app.example.com", "shop.example.com"], "appearance": {"theme": "dark"}}'

The answer is the widget as in Read one widget. A request that sends nothing to change answers 422. The plan's hostname limit counts the hostnames the widget keeps and the ones the request adds, so swapping one hostname for another at the limit works, and adding one more does not.

Rotate a secret key when it may have leaked, or when you lose it. grace says what happens to the old key:

2h
The old secret key keeps working for 2 hours, so you can change your server without a gap. This is the default.
none
The old secret key stops working at once.
Shell
curl -X POST https://api.coritan.com/api/v1/client/wicket/widgets/12/secret \
  -H "Authorization: Bearer $CORITAN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"grace": "2h"}'

The answer has the same shape as adding a widget: the widget, with secret_rotated_at and, for 2h, previous_secret_expires_at, and the new secret, shown once. A widget remembers one old secret key, so rotating again during a grace period ends the earlier key at once. We email the account owner each time a secret key is rotated.

Shell
curl -X DELETE https://api.coritan.com/api/v1/client/wicket/widgets/12 \
  -H "Authorization: Bearer $CORITAN_TOKEN"
JSON
{"status": "deleted"}

Warning

The widget's site key and secret key stop working at once, so a form that uses the widget stops passing its check. A deleted widget cannot be brought back.

Shell
curl "https://api.coritan.com/api/v1/client/wicket/widgets/12/analytics?range=7d" \
  -H "Authorization: Bearer $CORITAN_TOKEN"

range is 24h (the default), 7d, 30d, 90d or 365d. Any other word answers 422, and the message names the five allowed ones, such as range must be one of: 24h, 7d, 30d, 90d, 365d, not '1y'.

JSON
{
  "range": "7d",
  "from": "2026-10-11T16:00:00Z",
  "to": "2026-10-17T16:21:09Z",
  "granularity": "hour",
  "allowed_days": 7,
  "clamped": false,
  "upgrade": null,
  "series": [
    {"t": "2026-10-17T15:00:00Z", "issued": 52, "passed": 50, "passed_interactive": 3, "failed": 1, "interactive_shown": 4}
  ],
  "totals": {
    "issued": 1204,
    "passed": 1180,
    "passed_interactive": 40,
    "failed": 9,
    "interactive_shown": 51,
    "unsolved": 15,
    "solve_rate": 0.98,
    "verify": {"valid": 1150, "invalid": 4, "expired": 12, "duplicate": 2}
  },
  "top": {
    "hostname": [{"value": "example.com", "issued": 900, "passed": 890, "failed": 3}],
    "action": [],
    "country": [],
    "browser": [],
    "os": []
  }
}
Field Meaning
series One point for each hour (granularity is hour, for 24h and 7d) or each day (day, for 30d, 90d and 365d), with a point of zeros for a period with no challenges. t is the start of the period.
totals The sums over the series. unsolved is issued minus passed minus failed: challenges nobody finished. passed_interactive counts passes that needed a tick.
totals.verify What your server's verify calls found: tokens that were valid, invalid, expired or used twice (duplicate). A verify of zeros while passed is high means your server is not verifying tokens.
top The ten values with the most challenges for each of hostname, action, country, browser and os, over the same span.

When the range is longer than your plan reads back, the answer is still 200. It covers the days your plan allows, clamped is true, allowed_days is that number of days, and upgrade.plan names the plan that allows the range you asked for:

JSON
{"range": "90d", "allowed_days": 7, "clamped": true, "upgrade": {"plan": "plan-business"}}

The plan is the smallest one above yours that allows the range. 365d on any plan but Enterprise names plan-enterprise, and Enterprise reads every range in full.

Shell
curl https://api.coritan.com/api/v1/client/wicket/summary \
  -H "Authorization: Bearer $CORITAN_TOKEN"
JSON
{
  "widgets": 3,
  "limit": 25,
  "issued_24h": 1580,
  "passed_24h": 1522,
  "solve_rate_24h": 0.9633,
  "attention": [{"widget_id": 12, "name": "Sign-up form", "reason": "not_verifying", "disabled_reason": null}]
}

widgets is how many widgets the account has and limit how many its plan allows (-1 is unlimited). The counts cover the last 24 hours and every widget. attention has an entry for each widget that needs attention and each reason, in the same shape as in the list: widget_id, name, reason and disabled_reason, which is set only when reason is disabled.

The widget exists, lists the hostnames you gave it, and answers challenges from pages on those hostnames. Your server verifies the tokens at embed.siteverify_url with the secret key.

402 with "code": "entitlement_exceeded"
The request would take the account past a limit of its plan. detail.entitlement is wicket_widgets (widgets on the account), wicket_hostnames (hostnames on one widget) or wicket_unbranded (a widget without Wicket's name). detail.upgrade.plan is the plan that raises it. No add-on does, so upgrade.addon is null.
403 with "code": "role_refused"
You acted for another account as a team member whose role cannot make changes. Only the Admin and Technical roles can.
404
The widget does not exist or belongs to another account. Check the id, and the X-Coritan-Account header if you act for another account.
422
A value was refused. detail is a sentence that names the value and the reason, such as a hostname with a port or an appearance setting that does not exist.
429
The account made more than 60 changes in 10 minutes. Wait for the number of seconds in the Retry-After header.

API operations on this page

MethodPathWhat it does
GET/api/v1/client/wicket/widgetsList your widgets, with how each did in the last 24 hours and why any needs a look
POST/api/v1/client/wicket/widgetsAdd a widget
GET/api/v1/client/wicket/widgets/{widget_id}Get widget
PATCH/api/v1/client/wicket/widgets/{widget_id}Change a widget's name, hostnames, mode, appearance or branding
DELETE/api/v1/client/wicket/widgets/{widget_id}Delete a widget
POST/api/v1/client/wicket/widgets/{widget_id}/secretMake a new secret key for a widget
GET/api/v1/client/wicket/widgets/{widget_id}/analyticsWidget analytics
GET/api/v1/client/wicket/summaryWidgets summary