API Reference
The Stepcheck base URL is https://api-stepcheck.plxi.us. Every endpoint accepts and returns JSON and is authenticated with your API key.
curl -X POST https://api-stepcheck.plxi.us/v1/example/analyze \
-H "X-API-Key: sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"text": "The quick brown fox jumps over the lazy dog. The dog sleeps.",
"top": 3
}'Authentication
Send an X-API-Key header with every request. Keys are created and revoked in the dashboard; a key is shown once, when it is created, so store it somewhere safe.
X-API-Key: sk_live_...
Plans & limits
Limits apply per account, not per key: every key on the account (and teammates working in it) shares them. There is a per-minute rate, a cap on requests in flight at once, and a monthly quota. On the Free plan the quota resets on the 1st of each month (UTC); on paid plans it resets on your billing-period anniversary. Requests that fail validation are never charged.
| Plan | Price | Requests / min | In flight | Requests / month |
|---|---|---|---|---|
| Free | Free | 10 | 1 | 100 |
| Starter | $10/mo | 60 | 3 | 2,000 |
| Pro | $50/mo | 300 | 6 | 20,000 |
Errors
Errors return JSON with a stable error code, an optional message, and, on rate-limit and monthly-quota 429s, reset_at. A request that returns an error is never charged.
{
"error": "rate_limit_exceeded",
"reset_at": "2026-07-22T18:04:00Z"
}Fix the request
| Status | Code | When | What to do |
|---|---|---|---|
| 400 | bad_request | The body is not valid JSON. | Send the body as JSON. |
| 400 | invalid_input | A body field failed the operation’s checks; message says which one and why. | Correct that field. |
| 401 | invalid_api_key | The X-API-Key header is missing, malformed, revoked or not a key we issued. | Check the key, or create a new one in the dashboard. |
| 403 | account_blocked | The account is suspended. | Contact [email protected]. |
| 404 | not_found | No endpoint at that path for that method: operations take POST, /v1/usage takes GET. | Check the path and the method. |
| 413 | payload_too_large | The body is larger than the endpoint takes: 1 MiB unless it says otherwise. | Send a smaller body. |
Wait, then retry
| Status | Code | When | What to do |
|---|---|---|---|
| 429 | rate_limit_exceeded | The per-minute request limit is reached (per account, across all its keys). Requests that then fail, for example on invalid input, count toward it too. | Retry after reset_at, the start of the next minute. |
| 429 | concurrency_limit_exceeded | The account has as many requests in flight as its plan allows. | Retry with backoff once one of them finishes. There is no reset_at. |
| 429 | monthly_quota_exceeded | The quota for this billing period is used up. | Retry after reset_at: the 1st of next month (UTC) on Free, the end of your billing period on paid plans. Or upgrade. |
Temporary: retry with backoff
| Status | Code | When | What to do |
|---|---|---|---|
| 400 | body_read_failed | The connection failed while the body was being sent. | Retry. |
| 408 | request_body_timeout | The body took too long to arrive. | Retry, sending the whole body at once. |
| 500 | internal_error | Something failed on our side. | Retry with backoff. |
| 503 | capacity_unavailable | The API is handling as many requests as it can. | Retry with backoff. |
| 503 | database_unavailable limits_unavailable | Our database, or the store that counts rate limits, is unavailable. | Retry with backoff. |
| 503 | auth_busy | API key checks are busy. | Retry with backoff. |
| 503 | billing_period_stale billing_period_invalid | On a paid plan, the current billing period can’t be confirmed, for example because a renewal hasn’t been recorded yet. | Retry later. If it lasts, the account owner can open Billing in the dashboard to refresh it, or contact [email protected]. |
| 504 | request_timeout | The operation ran past the API’s time limit. | Retry with backoff; if it keeps happening, send smaller input. |
Use with AI agents (MCP)
Stepcheck is also an MCP server, so Claude and other AI agents can call every endpoint below as a tool. Tool calls are charged exactly like API calls, to the same plan and quota, and failed calls are free.
https://api-stepcheck.plxi.us/mcp
Claude (claude.ai and the desktop app): Settings → Connectors → Add custom connector, then paste the URL above. Claude asks you to sign in with your Plexsicity account; calls are then charged to your own account.
Claude Code, Cursor, VS Code and other clients: send an API key from the dashboard as a header. Calls are charged to the account that owns the key, so a team’s key spends the team’s quota. Clients that only take a bearer token can send Authorization: Bearer sk_live_… instead.
claude mcp add --transport http stepcheck https://api-stepcheck.plxi.us/mcp \
--header "X-API-Key: sk_live_..."POST/v1/example/analyze
Word, sentence and character counts, reading time and the most used words. Costs 1 request.
| Field | Type | Description |
|---|---|---|
| text | string | Required. The text to analyze. |
| top? | integer | How many of the most used words to list. Default 5. |
{
"text": "The quick brown fox jumps over the lazy dog. The dog sleeps.",
"top": 3
}{
"characters": 60,
"characters_no_spaces": 49,
"credits": 1,
"paragraphs": 1,
"reading_time_seconds": 4,
"sentences": 2,
"top_words": [
{
"count": 2,
"word": "dog"
},
{
"count": 1,
"word": "brown"
},
{
"count": 1,
"word": "fox"
}
],
"words": 12
}curl -X POST https://api-stepcheck.plxi.us/v1/example/analyze \
-H "X-API-Key: sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"text": "The quick brown fox jumps over the lazy dog. The dog sleeps.",
"top": 3
}'The response is the operation’s result plus credits, what the request was charged. Try it in the Playground. MCP tool: analyze_text.
POST/v1/example/slugify
Turn text into a URL-safe slug (accents removed, lowercase). Costs 1 request.
| Field | Type | Description |
|---|---|---|
| max_length? | integer | The longest slug to return, in characters. Default 80. |
| separator? | "-" | "_" | "." | The character between words. Default "-". |
| text | string | Required. The title or phrase to turn into a slug. |
{
"max_length": 80,
"separator": "-",
"text": "Héllo Wörld: a new post!"
}{
"credits": 1,
"slug": "hello-world-a-new-post"
}curl -X POST https://api-stepcheck.plxi.us/v1/example/slugify \
-H "X-API-Key: sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"max_length": 80,
"separator": "-",
"text": "Héllo Wörld: a new post!"
}'The response is the operation’s result plus credits, what the request was charged. Try it in the Playground. MCP tool: slugify_text.
GET/v1/usage
Your plan and usage this billing period, for alerting before you reach the quota.
curl https://api-stepcheck.plxi.us/v1/usage \
-H "X-API-Key: sk_live_..."{
"plan": "starter",
"used": 487,
"limit": 2000,
"period_start": "2026-07-18T09:12:44Z",
"period_end": "2026-08-18T09:12:44Z"
}Webhooks
Add an HTTPS endpoint under Webhooks in the dashboard and Stepcheck POSTs a request.completed event to it after each charged request. Each endpoint gets its own signing secret (whsec_…), shown once when you add it.
POST /your/webhook HTTP/1.1
Content-Type: application/json
User-Agent: Stepcheck-Webhooks/1
X-Stepcheck-Event: request.completed
X-Stepcheck-Event-ID: 6f1c2a7e-…:0d4e9b2c-…
X-Stepcheck-Signature: sha256=3b5f0c9e41d7…
{"data": {"ms": 4, "credits": 1, "endpoint": "example.analyze"}, "event": "request.completed", "created_at": "2026-09-29T14:03:11.482107+00:00"}X-Stepcheck-Signature is sha256=<hex>: the HMAC-SHA256 of the raw body, keyed with the endpoint’s full secret including whsec_. Compute it over the bytes you received, before parsing:
import crypto from 'node:crypto';
// rawBody: the request body exactly as received (Buffer or string). Verify it
// before parsing -- JSON that has been parsed and re-serialised won't match.
// secret: the endpoint's full signing secret, "whsec_" prefix included.
function verifySignature(rawBody, signatureHeader, secret) {
const expected =
'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const given = Buffer.from(String(signatureHeader || ''));
const want = Buffer.from(expected);
return given.length === want.length && crypto.timingSafeEqual(given, want);
}
// Express: take this route's body raw, verify, then parse.
app.post('/your/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const ok = verifySignature(req.body, req.get('X-Stepcheck-Signature'), process.env.WEBHOOK_SECRET);
if (!ok) return res.sendStatus(401);
const { event, data } = JSON.parse(req.body);
res.sendStatus(200);
});X-Stepcheck-Event-ID, so use it to ignore duplicates. Recent attempts are listed under Deliveries on the Webhooks page. Endpoints must be public HTTPS URLs.