clerk-backend-api
Skill16 starsChanged 24 days agoMITView on GitHub
Clerk Backend REST API explorer and executor. Browse tags, inspect endpoint schemas, and execute authenticated requests. Use when listing users, managing organizations, or calling any Clerk API endpoint.
- Pipes a download into a shell
- Reads credentials
- Sends data out
We split this skill into the rules it gives an agent
16 rules. 10 are common, 0 are distinctive, and 6 are very specific to this project.
10
Common
Most of the 5 edited versions we read keep these.
0
Distinctive
This author's own choices. Worth a look.
6
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Common: line 15
Before ANY POST / PATCH / PUT / DELETE, you MUST do ALL of the following in your response:
- Common: line 21
If empty, stop and ask the user.
- Common: line 31
For metadata operations: always explain which metadata type is being used and why (see Metadata types section below).
- Very specific: line 60
Roles: use "org:admin" or "org:member" (always prefix with org:).
- Common: line 179
Always warn the user this is permanent and confirm before proceeding.
- Very specific: line 207
ALWAYS execute requests with direct
curlcommands: Use the spec-extraction scripts (api-specs-context.sh, extract-tags.js, … - Common: line 235
Before doing anything outside the FAST PATH, fetch the available spec versions and tags by running:
- Common: line 240
Use the output to determine the latest version and available tags.
- Common: line 242
Caching: If you already fetched the spec context earlier in this conversation, do NOT fetch it again.
- Very specific: line 249
Always disregard endpoints/schemas related to platform.
- Very specific: line 250
Always confirm before performing write requests (POST/PUT/PATCH/DELETE).
- Common: line 255
Use direct curl commands for all API calls.
- Very specific: line 311
Use the LATEST VERSION from API specs context by default.
- Common: line 352
If using a non-latest version, fetch tags for that version:
- Common: line 400
Run the mandatory checks from the CRITICAL section above.
- Very specific: line 402
Ask the user for any required path/query/body parameters that weren't provided.
“Common” is measured: kept by at least 6 in 10 of the 5 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Options context
User Prompt: $ARGUMENTS
CRITICAL: Mandatory checks before EVERY write request
Before ANY POST / PATCH / PUT / DELETE, you MUST do ALL of the following in your response:
- Check CLERK_SECRET_KEY — verify it is set:
If empty, stop and ask the user. Do not proceed without a valid key.echo $CLERK_SECRET_KEY | head -c 10 - Check CLERK_BAPI_SCOPES — run:
Inspect the output. If scopes are missing or do not include the required write permission, tell the user: "This is a write operation and your current scopes may not allow it. Rerun with --admin to bypass?" Do NOT attempt the request and fail — ask first.echo $CLERK_BAPI_SCOPES - For DELETE requests: warn explicitly that the action is IRREVERSIBLE and list exactly what data will be permanently destroyed (user record, all sessions, all memberships, all associated data). Require explicit confirmation before proceeding. This warning is MANDATORY — never skip it.
- For metadata operations: always explain which metadata type is being used and why (see Metadata types section below).
FAST PATH: Common operations (use directly, no spec fetching needed)
For the operations below, skip spec fetching and execute immediately using these exact templates. Substitute $CLERK_SECRET_KEY, $USER_ID, $ORG_ID, $EMAIL as needed from the user's context.
Create organization + invite member (two-step)
# Step 1 — Create organization
ORG=$(curl -s -X POST "https://api.clerk.com/v1/organizations" \
-H "Authorization: Bearer $CLERK_SECRET_KEY" \
-H "Content-Type: application/json" \
-d "{\"name\": \"Acme Corp\", \"created_by\": \"$USER_ID\"}")
echo "$ORG" | python3 -c "import sys,json; d=json.load(sys.stdin); print(json.dumps(d, indent=2))"
# Step 2 — Extract org ID
ORG_ID=$(echo "$ORG" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
# Step 3 — Invite member with role
curl -s -X POST "https://api.clerk.com/v1/organizations/${ORG_ID}/invitations" \
-H "Authorization: Bearer $CLERK_SECRET_KEY" \
-H "Content-Type: application/json" \
-d "{\"email_address\": \"user@example.com\", \"role\": \"org:admin\"}" \
| python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin), indent=2))"Roles: use "org:admin" or "org:member" (always prefix with org:).
SDK equivalent (for Next.js / TypeScript projects with @clerk/nextjs or @clerk/backend)
import { clerkClient } from '@clerk/nextjs/server'
// OR if using @clerk/backend directly:
// import { createClerkClient } from '@clerk/backend'
// const clerkClient = createClerkClient({ secretKey: process.env.CLERK_SECRET_KEY })
// Step 1: Create organization
const org = await clerkClient.organizations.createOrganization({
name: 'Acme Corp',
createdBy: userId, // required — the ID of the user creating the org
})
// Step 2: Invite member to the org
const invitation = await clerkClient.organizations.createOrganizationInvitation({
organizationId: org.id,
emailAddress: 'user@example.com',
role: 'org:admin', // or 'org:member'
})Update user metadata
Always explain the three metadata types before asking which to use:
| Type | Field | Readable by | Writable by | Use for |
| Public | public_metadata | Client + Server | Server only | Plan tier, roles, feature flags the frontend reads |
| Private | private_metadata | Server only | Server only | Stripe IDs, compliance flags, internal identifiers |
| Unsafe | unsafe_metadata | Client + Server | Client + Server | Ephemeral UI state, onboarding steps (client-writable — avoid sensitive data) |
For plan: 'pro' and onboarded: true — use public_metadata (frontend-readable, server-writable):
curl -s -X PATCH "https://api.clerk.com/v1/users/${USER_ID}" \
-H "Authorization: Bearer $CLERK_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"public_metadata": {"plan": "pro", "onboarded": true}}' \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(f'Updated user {d[\"id\"]}: public_metadata={d.get(\"public_metadata\")}')"SDK equivalent:
import { clerkClient } from '@clerk/nextjs/server'
// OR: import { createClerkClient } from '@clerk/backend'
await clerkClient.users.updateUser(userId, {
publicMetadata: { plan: 'pro', onboarded: true }, // readable by client, writable server-only
// privateMetadata: { stripeId: 'cus_xxx' }, // server-only read AND write
// unsafeMetadata: { step: 'welcome' }, // client-writable, avoid sensitive data
})Note: REST API uses snake_case (public_metadata). SDK uses camelCase (publicMetadata).
List users (last 7 days)
curl -s "https://api.clerk.com/v1/users?limit=100&offset=0&order_by=-created_at&created_at=gt:$(date -d '7 days ago' +%s 2>/dev/null || date -v-7d +%s)000" \
-H "Authorization: Bearer $CLERK_SECRET_KEY" \
| python3 -c "
import sys, json
data = json.load(sys.stdin)
if isinstance(data, list):
print(f'Found {len(data)} users:')
for u in data:
print(f' {u[\"id\"]}: {u.get(\"email_addresses\", [{}])[0].get(\"email_address\", \"no email\")}')
else:
print(json.dumps(data, indent=2))
"Delete user (confirm required)
# ONLY run after explicit user confirmation
curl -s -X DELETE "https://api.clerk.com/v1/users/${USER_ID}" \
-H "Authorization: Bearer $CLERK_SECRET_KEY" \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(f'Deleted: {d}')"Clerk Backend API — Full Endpoint Reference
Base URL: https://api.clerk.com/v1
Auth: Authorization: Bearer $CLERK_SECRET_KEY on every request.
Users
List users
GET /v1/users
Query params: limit (max 500, default 10), offset, order_by (+/-created_at, +/-updated_at, +/-email_address, +/-web3wallet, +/-first_name, +/-last_name, +/-phone_number, +/-username, +/-last_active_at, +/-last_sign_in_at), email_address[], phone_number[], username[], web3wallet[], user_id[], query, created_at (ISO 8601 range: gt:TIMESTAMP or lt:TIMESTAMP in Unix ms)
Returns: array of User objectsGet user
GET /v1/users/{user_id}
Returns: User objectUpdate user
PATCH /v1/users/{user_id}
Body (JSON, snake_case): { public_metadata, private_metadata, unsafe_metadata, first_name, last_name, username, ... }Delete user — IRREVERSIBLE
DELETE /v1/users/{user_id}
Destroys: user record, all sessions, all memberships, all associated data
Returns: { id, object, deleted: true }Always warn the user this is permanent and confirm before proceeding.
Organizations
Create organization
POST /v1/organizations
Body: { name: string, created_by: string (user_id), public_metadata?, private_metadata?, max_allowed_memberships? }
Returns: Organization object with { id, name, slug, ... }List organizations
GET /v1/organizations
Query params: limit, offset, query, order_byInvite member
POST /v1/organizations/{organization_id}/invitations
Body: { email_address: string, role: string ("org:admin" or "org:member"), public_metadata?, private_metadata? }
Returns: OrganizationInvitation objectHow to execute requests
ALWAYS execute requests with direct curl commands. Use the spec-extraction scripts (api-specs-context.sh, extract-tags.js, extract-endpoint-detail.sh) to discover endpoints, but make actual API calls with curl.
Template for GET requests:
curl -s "https://api.clerk.com/v1${PATH}${QUERY_STRING}" \
-H "Authorization: Bearer $CLERK_SECRET_KEY"Template for POST/PATCH requests:
curl -s -X ${METHOD} "https://api.clerk.com/v1${PATH}" \
-H "Authorization: Bearer $CLERK_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '${BODY_JSON}'Template for DELETE requests:
curl -s -X DELETE "https://api.clerk.com/v1${PATH}" \
-H "Authorization: Bearer $CLERK_SECRET_KEY"After getting the response: Parse and display it clearly. Use python3 -c "import sys,json; data=json.load(sys.stdin); print(json.dumps(data, indent=2))" to pretty-print JSON. Extract key fields (id, email, name, etc.) and summarize them for the user.
API specs context
Before doing anything outside the FAST PATH, fetch the available spec versions and tags by running:
bash scripts/api-specs-context.shUse the output to determine the latest version and available tags.
Caching: If you already fetched the spec context earlier in this conversation, do NOT fetch it again. Reuse the version and tags from the previous call.
Rules
- For common operations (list users, create org, invite, update metadata, delete user): use the FAST PATH above — do NOT fetch specs first.
- Always disregard endpoints/schemas related to
platform. - Always confirm before performing write requests (POST/PUT/PATCH/DELETE).
- For DELETE operations, always warn the user that the action is irreversible and mention what data will be lost (user record, sessions, memberships). This warning is MANDATORY — never skip it.
- For write operations (POST/PUT/PATCH/DELETE), check
CLERK_BAPI_SCOPESbefore attempting the request. If missing or insufficient, ask the user upfront. Do NOT attempt and fail — ask before executing. This check is MANDATORY. - For metadata operations, always explain all three types (public, private, unsafe) and recommend the appropriate one.
- Pagination: always use
limit+offsetand mention that results may be paginated for large datasets. - Use direct curl commands for all API calls.
Rate Limits & Gotchas
Rate Limits
| Environment | Limit |
| Production | 1,000 requests / 10 seconds |
| Development | 100 requests / 10 seconds |
| Single invitations | 100 / hour |
| Bulk invitations | 25 / hour |
| Org invitations | 250 / hour |
| Frontend API sign-in creation | 5 / 10 seconds |
| Frontend API sign-in attempts | 3 / 10 seconds |
| List users max per page | 500 |
currentUser() makes a real API call that counts against rate limits. Use auth() for just the session claims — it reads from the token without an API call.
Metadata Overwrites (Not Merges)
updateUser({ publicMetadata: { role: 'admin' } }) REPLACES all public metadata, not merges. To add a field without losing existing data: read first, spread, then write.
Wrong:
await clerkClient.users.updateUser(userId, { publicMetadata: { newField: 'value' } })This DELETES all other publicMetadata fields.
Right:
const user = await clerkClient.users.getUser(userId)
await clerkClient.users.updateUser(userId, {
publicMetadata: { ...user.publicMetadata, newField: 'value' },
})Modes
Determine the active mode based on the user prompt in Options context:
| Mode | Trigger | Behavior |
help | Prompt is empty, or contains only help / -h / --help | Print usage examples (step 0) |
browse | Prompt is tags, or a tag name (e.g. Users) | List all tags or endpoints for a tag |
execute | Specific endpoint (e.g. GET /users) or natural language action (e.g. "get user john_doe") | Look up endpoint, execute request |
detail | Endpoint + help / -h / --help (e.g. GET /users help) | Show endpoint schema, don't execute |
Your Task
Use the LATEST VERSION from API specs context by default. If the user specifies a different version (e.g. --version 2024-10-01), use that version instead.
Determine the active mode, then follow the applicable steps below.
0. Print usage
Modes: help only — Skip for browse, execute, and detail.
Print the following examples to the user verbatim:
Browse
/clerk-backend-api tags — list all tags
/clerk-backend-api Users — browse endpoints for the Users tag
/clerk-backend-api Users version 2025-11-10.yml — browse using a different version
Execute
/clerk-backend-api GET /users — fetch all users
/clerk-backend-api get user john_doe — natural language works too
/clerk-backend-api POST /invitations — create an invitation
Inspect
/clerk-backend-api GET /users help — show endpoint schema without executing
/clerk-backend-api POST /invitations -h — view request/response details
Options
--admin — bypass scope restrictions for write/delete
--version [date], version [date] — use a specific spec version
--help, -h, help — inspect endpoint instead of executingStop here.
1. Fetch tags
Modes: browse (when prompt is tags or no tag specified) — Skip for help, execute, and detail.
If using a non-latest version, fetch tags for that version:
curl -s https://raw.githubusercontent.com/clerk/openapi-specs/main/bapi/${version_name} | node scripts/extract-tags.jsOtherwise, use the TAGS already in API specs context.
Share tags in a table and prompt the user to select a query.
2. Fetch tag endpoints
Modes: browse (when a tag name is provided) — Skip for help, execute, and detail.
Fetch all endpoints for the identified tag:
curl -s https://raw.githubusercontent.com/clerk/openapi-specs/main/bapi/${version_name} | bash scripts/extract-tag-endpoints.sh "${tag_name}"Share the results (endpoints, schemas, parameters) with the user.
3. Fetch endpoint detail
Modes: execute, detail — Skip for help and browse.
For natural language prompts in execute mode, first check if the operation matches a FAST PATH entry above. If it does, skip this step and proceed directly to step 4 using the FAST PATH template.
For other endpoints, identify the matching endpoint by searching the tags in context. Fetch tag endpoints if needed to resolve the exact path and method.
Extract the full endpoint definition:
curl -s https://raw.githubusercontent.com/clerk/openapi-specs/main/bapi/${version_name} | bash scripts/extract-endpoint-detail.sh "${path}" "${method}"${path}— e.g./users/{user_id}${method}— lowercase, e.g.get
detail mode: Share the endpoint definition and schemas with the user. Stop here.
execute mode: Continue to step 4.
4. Execute request
Modes: execute only.
- Run the mandatory checks from the CRITICAL section above.
- Identify required and optional parameters from the spec (step 3) or FAST PATH.
- Ask the user for any required path/query/body parameters that weren't provided.
- Build and execute a direct curl command (see How to execute requests above).
- Parse the JSON response and display it clearly. Extract and summarize key fields for the user.
Example — list users and parse response:
RESPONSE=$(curl -s "https://api.clerk.com/v1/users?limit=10" \
-H "Authorization: Bearer $CLERK_SECRET_KEY")
echo "$RESPONSE" | python3 -c "
import sys, json
data = json.load(sys.stdin)
if isinstance(data, list):
print(f'Found {len(data)} users:')
for u in data:
print(f' {u[\"id\"]}: {u.get(\"email_addresses\", [{}])[0].get(\"email_address\", \"no email\")}')
else:
print(json.dumps(data, indent=2))
"See Also
clerk-setup- Initial Clerk installclerk-orgs- Manage organizations via APIclerk-webhooks- Real-time event sync
Other versions6 projects use a version of this.
Word for word · 1
- clerk/skills★ 89100% same
Edited · 5
- adrianhajdin/ghost-ai★ 33297% same
- delorenj/skills★ 1877% same
- Aymenjdily/biblion-testing-agentic-ai-skills★ 298% same
- sanketbodke/skew-ai★ 098% same
- Visitha2001/ghost-ai-sse★ 098% same
Wrote their own version · 0
None found.
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
Used alongsideProjects with it also use clerk-billing.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
sanketbodkeSkillclerk-billing
Clerk Billing for subscription management - render Clerk's PricingTable
Also in 3 projects that use this
In this repository17 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- clerk-cliSkill
- clerk-custom-uiSkill
- clerk-expoSkill
- clerk-orgsSkill
- clerk-setupSkill
- clerk-testingSkill
- clerk-webhooksSkill
- create-agent-with-sanity-contextSkill
- dial-your-contextSkill
- expo-animationSkill
- expo-data-fetchingSkill
- expo-design-systemSkill
- expo-project-structureSkill
- expo-routerSkill
- shape-your-agentSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

