Site Members & Access
Site members are the people who sign in to your site — not the people who sign in to SpiderPublish to build it. They live in their own store, and a site member never becomes a dashboard user, never joins your workspace and never appears in your team list.
This page documents the 23 endpoints behind the Members area of the dashboard: members themselves, member groups, per-page access, data restrictions and single sign-on.
Authentication
Every route on this page accepts either a bearer project token or a dashboard session cookie. The bearer form is the one you want from a script or an agent:
https://spideriq.ai/api/v1/dashboard/...
Authorization: Bearer $PROJECT_TOKEN$PROJECT_TOKEN is either a client_id:api_key:api_secret triplet or a PAT. Send no token and you get a 401.
There is no members command in the CLI and no members_* tool in the MCP server. This REST API is the agent surface for the feature.
Scoping
Member operations resolve against a project — the site whose members you are managing. A bearer token resolves its own project; a dashboard session resolves the project you have selected. If neither yields one, every route returns 400 with A project must be selected to manage site members.
Two routes additionally need a client scope (GET /dashboard/members/quota and POST /dashboard/members/invite); without one they return 400 with A client scope is required.
Errors common to every route
These apply to all 23 endpoints and are not repeated in full below.
Status | When | Resolution |
|---|---|---|
| no | send |
| no project could be resolved | select a project in the dashboard, or use a token scoped to one |
| the path does not exist | check the path — a good token on a bogus path returns |
| the members service did not answer |
|
| a body field failed validation | the response names the field and the constraint |
Members
GET /dashboard/members
List the site's members.
Parameters
Name | Type | Required | Description |
|---|---|---|---|
| string | no | free-text filter, ≤200 chars |
| integer | no | page size, 1–200, default |
| integer | no | rows to skip, ≥0, default |
Example
curl "https://spideriq.ai/api/v1/dashboard/members?limit=50" \
-H "Authorization: Bearer $PROJECT_TOKEN"Response — 200
{ "members": [], "total": 0 }Errors:
Status | When | Resolution |
|---|---|---|
| no or invalid | send |
| no project could be resolved | select a project, or use a project-scoped token |
| the path does not exist | check the path — a good token on a bogus path returns |
|
| transient; retry — the request was not applied |
GET /dashboard/members/quota
Report whether the plan allows another member, and where the count currently sits.
Parameters — none.
Example
curl "https://spideriq.ai/api/v1/dashboard/members/quota" \
-H "Authorization: Bearer $PROJECT_TOKEN"Response — 200. limit is null when the plan sets no ceiling.
{ "allowed": true, "limit": null, "current": 0 }Errors:
Status | When | Resolution |
|---|---|---|
| no client scope resolved |
|
| no or invalid | send |
| the path does not exist | check the path — a good token on a bogus path returns |
|
| transient; retry — the request was not applied |
POST /dashboard/members/invite
Create a member and, when you supply the site's own login URL, email them an invitation.
Parameters
Name | Type | Required | Description |
|---|---|---|---|
| string | yes | 3–254 chars; must contain |
| string | no | ≤200 chars |
| string | no | ≤64 chars |
| string[] | no | up to 50 group slugs |
| string | no | ≤200 chars; used in the invitation email |
| string | no | ≤2048 chars. Must be your own site's login page. Omit it and the member is created but no email is sent |
Example
curl -X POST "https://spideriq.ai/api/v1/dashboard/members/invite" \
-H "Authorization: Bearer $PROJECT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "ada@example.com",
"name": "Ada Lovelace",
"groups": ["members"],
"site_name": "Ada'\''s Club",
"login_url": "https://club.example.com/login"
}'Response — 201. The created member, plus emailed telling you whether an invitation actually went out.
{ "id": "...", "email": "ada@example.com", "status": "invited", "emailed": true }Errors:
Status | When | Resolution |
|---|---|---|
| the plan's member limit is reached | the message carries |
|
| must contain |
| no client scope resolved |
|
| no or invalid | send |
| the path does not exist | check the path — a good token on a bogus path returns |
|
| transient; retry — the request was not applied |
POST /dashboard/members/{user_id}/ban
Ban a member. Because a member's token lives at most fifteen minutes, a ban takes effect within one token lifetime rather than instantly.
Parameters — user_id (path, required): the member's id.
Example
curl -X POST "https://spideriq.ai/api/v1/dashboard/members/usr_123/ban" \
-H "Authorization: Bearer $PROJECT_TOKEN"Response — 200
{ "ok": true }Errors:
Status | When | Resolution |
|---|---|---|
| no or invalid | send |
| no project could be resolved | select a project, or use a project-scoped token |
| the path does not exist | check the path — a good token on a bogus path returns |
|
| transient; retry — the request was not applied |
POST /dashboard/members/{user_id}/unban
Restore a banned member and set the role they come back with.
Parameters
Name | Type | Required | Description |
|---|---|---|---|
| string (path) | yes | the member's id |
| string (body) | no | 1–64 chars, default |
Example
curl -X POST "https://spideriq.ai/api/v1/dashboard/members/usr_123/unban" \
-H "Authorization: Bearer $PROJECT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"role": "member"}'Response — 200
{ "ok": true }Errors:
Status | When | Resolution |
|---|---|---|
| no or invalid | send |
| no project could be resolved | select a project, or use a project-scoped token |
| the path does not exist | check the path — a good token on a bogus path returns |
|
| transient; retry — the request was not applied |
POST /dashboard/members/{user_id}/role
Set a member's role.
Parameters
Name | Type | Required | Description |
|---|---|---|---|
| string (path) | yes | the member's id |
| string (body) | yes | 1–64 chars |
Example
curl -X POST "https://spideriq.ai/api/v1/dashboard/members/usr_123/role" \
-H "Authorization: Bearer $PROJECT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"role": "editor"}'Response — 200
{ "ok": true }Errors:
Status | When | Resolution |
|---|---|---|
| no or invalid | send |
| no project could be resolved | select a project, or use a project-scoped token |
| the path does not exist | check the path — a good token on a bogus path returns |
|
| transient; retry — the request was not applied |
PATCH /dashboard/members/{user_id}
Update a member's status or group membership. Send only what you are changing.
Parameters
Name | Type | Required | Description |
|---|---|---|---|
| string (path) | yes | the member's id |
| string | no | one of |
| string[] | no | up to 50 group slugs; replaces the member's set |
Example
curl -X PATCH "https://spideriq.ai/api/v1/dashboard/members/usr_123" \
-H "Authorization: Bearer $PROJECT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"groups": ["members", "beta"]}'Response — 200
{ "ok": true }Errors:
Status | When | Resolution |
|---|---|---|
| neither |
|
|
| use |
| no or invalid | send |
| the path does not exist | check the path — a good token on a bogus path returns |
|
| transient; retry — the request was not applied |
Member groups
A group is how you name which members get through. Two groups are built in and cannot be edited or deleted: visitors and logged_in.
GET /dashboard/member-groups
List the project's groups, built-in ones included.
Parameters — none.
Example
curl "https://spideriq.ai/api/v1/dashboard/member-groups" \
-H "Authorization: Bearer $PROJECT_TOKEN"Response — 200
{
"groups": [
{ "slug": "visitors", "name": "Visitors", "builtin": true, "membership_type": "builtin" },
{ "slug": "logged_in", "name": "Logged-in users", "builtin": true, "membership_type": "builtin" }
]
}Errors:
Status | When | Resolution |
|---|---|---|
| no or invalid | send |
| no project could be resolved | select a project, or use a project-scoped token |
| the path does not exist | check the path — a good token on a bogus path returns |
|
| transient; retry — the request was not applied |
POST /dashboard/member-groups
Create a group. membership_type decides whether you maintain the list yourself or let a predicate decide.
Parameters
Name | Type | Required | Description |
|---|---|---|---|
| string | yes | 1–64 chars, the stable key you reference from page access and restrictions |
| string | yes | 1–200 chars, the display name |
| string | no | ≤16 chars, default |
| object | no | the rule evaluated at check time for a dynamic group |
Example
curl -X POST "https://spideriq.ai/api/v1/dashboard/member-groups" \
-H "Authorization: Bearer $PROJECT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"slug": "members", "name": "Paying members", "membership_type": "manual"}'Response — 201, the created group.
Errors:
Status | When | Resolution |
|---|---|---|
| the slug is already taken on this project | pick another slug, or patch the existing group |
| no or invalid | send |
| no project could be resolved | select a project, or use a project-scoped token |
| the path does not exist | check the path — a good token on a bogus path returns |
|
| transient; retry — the request was not applied |
PATCH /dashboard/member-groups/{group_id}
Rename a group, activate or deactivate it, or change its predicate.
Parameters
Name | Type | Required | Description |
|---|---|---|---|
| string (path) | yes | the group's id |
| string | no | 1–200 chars |
| boolean | no | deactivate without deleting |
| object | no | replaces the rule |
| boolean | no | default |
Example
curl -X PATCH "https://spideriq.ai/api/v1/dashboard/member-groups/grp_123" \
-H "Authorization: Bearer $PROJECT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Founding members", "active": true}'Response — 200, the updated group.
Errors:
Status | When | Resolution |
|---|---|---|
|
| check the id; built-in groups are not patchable |
| no or invalid | send |
| no project could be resolved | select a project, or use a project-scoped token |
|
| transient; retry — the request was not applied |
DELETE /dashboard/member-groups/{group_id}
Delete a group.
Parameters — group_id (path, required).
Example
curl -X DELETE "https://spideriq.ai/api/v1/dashboard/member-groups/grp_123" \
-H "Authorization: Bearer $PROJECT_TOKEN"Response — 204, no body.
Errors:
Status | When | Resolution |
|---|---|---|
|
| check the id; built-in groups cannot be deleted |
| no or invalid | send |
| no project could be resolved | select a project, or use a project-scoped token |
|
| transient; retry — the request was not applied |
Page access
Access is enforced at the edge, before the page is rendered. Every page is public until you say otherwise — turning members on does not gate anything you have not gated yourself.
GET /dashboard/pages/{page_id}/access
Read one page's access setting.
Parameters — page_id (path, required).
Example
curl "https://spideriq.ai/api/v1/dashboard/pages/23daf8be-8d91-458c-a33a-4eabcaba24b2/access" \
-H "Authorization: Bearer $PROJECT_TOKEN"Response — 200
{ "access": "public", "allowed_groups": [] }Errors:
Status | When | Resolution |
|---|---|---|
| no or invalid | send |
| no project could be resolved | select a project, or use a project-scoped token |
| the path does not exist | check the path — a good token on a bogus path returns |
|
| transient; retry — the request was not applied |
PUT /dashboard/pages/{page_id}/access
Set one page's access level.
Parameters
Name | Type | Required | Description |
|---|---|---|---|
| string (path) | yes | the page's id |
| string | yes | ≤32 chars — |
| string[] | no | up to 100 group slugs; the groups that may read the page |
Example
curl -X PUT "https://spideriq.ai/api/v1/dashboard/pages/23daf8be-8d91-458c-a33a-4eabcaba24b2/access" \
-H "Authorization: Bearer $PROJECT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"access": "group", "allowed_groups": ["members"]}'Response — 200, the stored access record.
Errors:
Status | When | Resolution |
|---|---|---|
| no or invalid | send |
| no project could be resolved | select a project, or use a project-scoped token |
| the path does not exist | check the path — a good token on a bogus path returns |
|
| transient; retry — the request was not applied |
Data restrictions
Gating a page is not enough on its own: a component on a public page can still fetch content you meant to keep back. A data restriction closes that gap for blog posts.
Version one is deliberately narrow. Only
source_id: "posts"withfield: "tag"andop: "in"is honoured at read time. Rows with any otherfieldoropare stored and ignored — they will not filter anything. This is not yet a general record-level filter.
The rule is deny by default: once posts carry any restriction, nothing is visible except what a rule allows, and a member sees the union of what all their groups allow.
GET /dashboard/data-restrictions
List the project's restrictions.
Parameters — none.
Example
curl "https://spideriq.ai/api/v1/dashboard/data-restrictions" \
-H "Authorization: Bearer $PROJECT_TOKEN"Response — 200
{ "restrictions": [] }Errors:
Status | When | Resolution |
|---|---|---|
| no or invalid | send |
| no project could be resolved | select a project, or use a project-scoped token |
| the path does not exist | check the path — a good token on a bogus path returns |
|
| transient; retry — the request was not applied |
POST /dashboard/data-restrictions
Create a restriction: this group may see posts carrying these tags.
Parameters
Name | Type | Required | Description |
|---|---|---|---|
| string | yes | 1–128 chars. Only |
| string | yes | 1–64 chars, the group the rule applies to |
| string | yes | 1–64 chars. Only |
| string | no | ≤16 chars, default |
| string[] | no | up to 500 values, each ≤256 chars — the tags this group may see |
Example
curl -X POST "https://spideriq.ai/api/v1/dashboard/data-restrictions" \
-H "Authorization: Bearer $PROJECT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"source_id": "posts",
"group_slug": "members",
"field": "tag",
"op": "in",
"allowed_values": ["members-only", "archive"]
}'Response — 201, the created restriction.
Errors:
Status | When | Resolution |
|---|---|---|
| an entry in | shorten it; the response names the constraint |
| no or invalid | send |
| no project could be resolved | select a project, or use a project-scoped token |
| the path does not exist | check the path — a good token on a bogus path returns |
|
| transient; retry — the request was not applied |
PATCH /dashboard/data-restrictions/{restriction_id}
Change a restriction's operator, values, or active state.
Parameters
Name | Type | Required | Description |
|---|---|---|---|
| string (path) | yes | the restriction's id |
| string | no | ≤16 chars |
| string[] | no | up to 500 values; replaces the list |
| boolean | no | disable without deleting |
Example
curl -X PATCH "https://spideriq.ai/api/v1/dashboard/data-restrictions/res_123" \
-H "Authorization: Bearer $PROJECT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"allowed_values": ["members-only"]}'Response — 200, the updated restriction.
Errors:
Status | When | Resolution |
|---|---|---|
| no or invalid | send |
| no project could be resolved | select a project, or use a project-scoped token |
| the path does not exist | check the path — a good token on a bogus path returns |
|
| transient; retry — the request was not applied |
DELETE /dashboard/data-restrictions/{restriction_id}
Delete a restriction. Remember deny-by-default: removing the last rule on a source makes that source unrestricted again.
Parameters — restriction_id (path, required).
Example
curl -X DELETE "https://spideriq.ai/api/v1/dashboard/data-restrictions/res_123" \
-H "Authorization: Bearer $PROJECT_TOKEN"Response — 204, no body.
Errors:
Status | When | Resolution |
|---|---|---|
| no or invalid | send |
| no project could be resolved | select a project, or use a project-scoped token |
| the path does not exist | check the path — a good token on a bogus path returns |
|
| transient; retry — the request was not applied |
Single sign-on
Members can always sign in with an email and password, a magic link or a one-time code. SSO adds an identity provider you register yourself. Google is available at the platform level; per-project providers are OIDC.
GET /dashboard/sso-config
Read the project's just-in-time provisioning policy — what happens the first time somebody signs in through a provider.
Parameters — none.
Example
curl "https://spideriq.ai/api/v1/dashboard/sso-config" \
-H "Authorization: Bearer $PROJECT_TOKEN"Response — 200. auto_provision: false is the default, which means SSO is invite-gated.
{ "auto_provision": false, "default_role": "member", "default_groups": [] }Errors:
Status | When | Resolution |
|---|---|---|
| no or invalid | send |
| no project could be resolved | select a project, or use a project-scoped token |
| the path does not exist | check the path — a good token on a bogus path returns |
|
| transient; retry — the request was not applied |
PUT /dashboard/sso-config
Set the provisioning policy. An omitted field is left unchanged, so you can send one key.
Parameters
Name | Type | Required | Description |
|---|---|---|---|
| boolean | no |
|
| string | no | 1–64 chars, the role a provisioned member gets |
| string[] | no | up to 50 slugs, each ≤64 chars |
Example
curl -X PUT "https://spideriq.ai/api/v1/dashboard/sso-config" \
-H "Authorization: Bearer $PROJECT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"auto_provision": true, "default_role": "member", "default_groups": ["staff"]}'Response — 200, the stored policy.
Errors:
Status | When | Resolution |
|---|---|---|
| no or invalid | send |
| no project could be resolved | select a project, or use a project-scoped token |
| the path does not exist | check the path — a good token on a bogus path returns |
|
| transient; retry — the request was not applied |
GET /dashboard/sso-providers
List the project's identity providers. Client secrets are never returned.
Parameters — none.
Example
curl "https://spideriq.ai/api/v1/dashboard/sso-providers" \
-H "Authorization: Bearer $PROJECT_TOKEN"Response — 200
{ "providers": [] }Errors:
Status | When | Resolution |
|---|---|---|
| no or invalid | send |
| no project could be resolved | select a project, or use a project-scoped token |
| the path does not exist | check the path — a good token on a bogus path returns |
|
| transient; retry — the request was not applied |
GET /dashboard/sso-providers/{provider_id}
Read one provider, enriched with the callback URLs to register at the identity provider's end.
Parameters — provider_id (path, required).
Example
curl "https://spideriq.ai/api/v1/dashboard/sso-providers/okta" \
-H "Authorization: Bearer $PROJECT_TOKEN"Response — 200. Includes the callback URLs derived from your verified domains; client_secret is omitted.
Errors:
Status | When | Resolution |
|---|---|---|
| no or invalid | send |
| no project could be resolved | select a project, or use a project-scoped token |
| the path does not exist | check the path — a good token on a bogus path returns |
|
| transient; retry — the request was not applied |
PUT /dashboard/sso-providers/{provider_id}
Create or replace a provider. This is an upsert, and client_secret is required on every save — the config is stored as one blob and the secret is never returned, so a save without it would erase it.
Parameters
Name | Type | Required | Description |
|---|---|---|---|
| string (path) | yes | your key for this provider |
| string | yes | 8–2048 chars, must start with |
| string | yes | 1–253 chars, the email domain routed to this provider |
| string | yes | 1–256 chars |
| string | yes | 1–1024 chars, required on every save |
| string | no | ≤2048 chars |
| string[] | no | up to 20 |
| boolean | no | |
| object | no | claim-to-field mapping |
Example
curl -X PUT "https://spideriq.ai/api/v1/dashboard/sso-providers/okta" \
-H "Authorization: Bearer $PROJECT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"issuer": "https://example.okta.com",
"domain": "example.com",
"client_id": "0oa1b2c3",
"client_secret": "'"$OKTA_SECRET"'",
"scopes": ["openid", "email", "profile"],
"pkce": true
}'Response — 200, the stored provider plus its callback URLs. client_secret is not echoed.
Errors:
Status | When | Resolution |
|---|---|---|
|
| plain |
|
| it is required on every save, not only the first |
| no or invalid | send |
| no project could be resolved | select a project, or use a project-scoped token |
| the path does not exist | check the path — a good token on a bogus path returns |
|
| transient; retry — the request was not applied |
DELETE /dashboard/sso-providers/{provider_id}
Remove a provider. Members who signed in through it keep their accounts; they can no longer use that route to sign in.
Parameters — provider_id (path, required).
Example
curl -X DELETE "https://spideriq.ai/api/v1/dashboard/sso-providers/okta" \
-H "Authorization: Bearer $PROJECT_TOKEN"Response — 204, no body.
Errors:
Status | When | Resolution |
|---|---|---|
| no or invalid | send |
| no project could be resolved | select a project, or use a project-scoped token |
| the path does not exist | check the path — a good token on a bogus path returns |
|
| transient; retry — the request was not applied |