SpiderBook
9 endpoints from the published OpenAPI import.
Public submit — confirm a booking OR persist a form submission (no auth)
Submit against an active flow.
SpiderFlow P1.W (2026-05-13) + P2.1 (2026-05-14) — the body schema is dispatched at runtime based on the flow's kind discriminator (mig 233):
kind='booking'/'commerce'— body must satisfy :class:BookingAnswers(slot_start / slot_end / hold_id / contact / consent + optional service_id / staff_id / extra). The Cal.com booking path + Turnstile + GDPR-booking-consent gates run as before.
kind='form'— body is a flat{field_id: value}map (or nested{step_id: {field_id: value}}); validated against the flow's FormStep fields. Cal.com / hold / Turnstile / booking-consent gates are NOT applied (forms are not bookings). The submission is persisted topublic.resultswithworker_type='booking', phase='final', data.kind='form'so analytics / CRM-sync handlers can pick it up without a new migration.
kind='funnel'(P2.1) — the public /submit endpoint is not applicable. Funnels sequence SpiderPublish pages and don't carry a single terminal submission event. Callers receive a structured 422submit_not_applicable_for_funnelenvelope. To collect data from a funnel visitor, embed a form sub-flow whose own/submitpersists the data.
Route-level dependencies are intentionally light; every check happens inside the handler so one handler serves the whole flow (G2→G15) and tests can exercise the full chain without wrangling FastAPI's dependency tree.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
flow_id | path | string | true |
Try it
Examples
cURL
curl -X POST 'https://spideriq.ai/api/v1/booking/{flow_id}/submit' \
-H 'Authorization: Bearer <token>'Python
import httpx
resp = httpx.post(
"https://spideriq.ai/api/v1/booking/{flow_id}/submit",
headers={"Authorization": "Bearer <token>"},
)
resp.raise_for_status()
print(resp.json())JavaScript
const resp = await fetch("https://spideriq.ai/api/v1/booking/{flow_id}/submit", {
method: "POST",
headers: { "Authorization": "Bearer <token>" }
});
const data = await resp.json();
console.log(data);Go
package main
import (
"net/http"
)
func main() {
req, _ := http.NewRequest("POST", "https://spideriq.ai/api/v1/booking/{flow_id}/submit", nil)
req.Header.Set("Authorization", "Bearer <token>")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
| Status | Description |
|---|---|
200 | Successful Response |
400 | Consent missing, invalid payload, or unknown / missing required form field. |
403 | Turnstile verification failed. |
404 | Flow not found or inactive. |
409 | Slot hold expired or Cal.com reported slot taken. |
422 | Body fails BookingAnswers validation (kind='booking' / 'commerce' only) OR kind='funnel' submit is not applicable. |
429 | IP rate limit exceeded. |
503 | Cal.com temporarily unavailable — retry. |
Fetch a booking flow descriptor (agent-facing)
Returns a minimal {flow_id, kind, name, status} descriptor for the given flow_id. Intended for agents (Claude Code, MCP tools) to discover a flow's kind before driving the full /render endpoint. On wrong kind, returns 409 with a suggested_url pointing at the right endpoint. On unknown id, returns 404 RESOURCE_NOT_FOUND.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
flow_id | path | string | true |
Try it
Examples
cURL
curl -X GET 'https://spideriq.ai/api/v1/booking/{flow_id}' \
-H 'Authorization: Bearer <token>'Python
import httpx
resp = httpx.get(
"https://spideriq.ai/api/v1/booking/{flow_id}",
headers={"Authorization": "Bearer <token>"},
)
resp.raise_for_status()
print(resp.json())JavaScript
const resp = await fetch("https://spideriq.ai/api/v1/booking/{flow_id}", {
method: "GET",
headers: { "Authorization": "Bearer <token>" }
});
const data = await resp.json();
console.log(data);Go
package main
import (
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://spideriq.ai/api/v1/booking/{flow_id}", nil)
req.Header.Set("Authorization", "Bearer <token>")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
| Status | Description |
|---|---|
200 | Successful Response |
404 | Flow not found (RESOURCE_NOT_FOUND envelope) |
409 | Flow is not a booking (WRONG_FLOW_KIND envelope) |
422 | Validation Error |
Fetch the public flow payload (localized via Accept-Language)
Returns the flow JSON the booking component mounts. Honours Accept-Language: labels/descriptions/button_label are swapped via translations[locale] with fallback to English. Unknown flows → 404.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
flow_id | path | string | true |
Try it
Examples
cURL
curl -X GET 'https://spideriq.ai/api/v1/booking/{flow_id}/render' \
-H 'Authorization: Bearer <token>'Python
import httpx
resp = httpx.get(
"https://spideriq.ai/api/v1/booking/{flow_id}/render",
headers={"Authorization": "Bearer <token>"},
)
resp.raise_for_status()
print(resp.json())JavaScript
const resp = await fetch("https://spideriq.ai/api/v1/booking/{flow_id}/render", {
method: "GET",
headers: { "Authorization": "Bearer <token>" }
});
const data = await resp.json();
console.log(data);Go
package main
import (
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://spideriq.ai/api/v1/booking/{flow_id}/render", nil)
req.Header.Set("Authorization", "Bearer <token>")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
| Status | Description |
|---|---|
200 | Successful Response |
422 | Validation Error |
Record a flow-render event (public, no auth)
Fire-and-forget analytics write called by the booking component on mount and step transitions. Never returns 5xx — analytics is non-critical.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
flow_id | path | string | true |
Try it
Examples
cURL
curl -X POST 'https://spideriq.ai/api/v1/booking/{flow_id}/render' \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{
"step_reached": "string"
}'Python
import httpx
resp = httpx.post(
"https://spideriq.ai/api/v1/booking/{flow_id}/render",
headers={"Authorization": "Bearer <token>", "Content-Type": "application/json"},
json={"step_reached": "string"},
)
resp.raise_for_status()
print(resp.json())JavaScript
const resp = await fetch("https://spideriq.ai/api/v1/booking/{flow_id}/render", {
method: "POST",
headers: { "Authorization": "Bearer <token>", "Content-Type": "application/json" },
body: JSON.stringify({"step_reached": "string"})
});
const data = await resp.json();
console.log(data);Go
package main
import (
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{"step_reached": "string"}`)
req, _ := http.NewRequest("POST", "https://spideriq.ai/api/v1/booking/{flow_id}/render", body)
req.Header.Set("Authorization", "Bearer <token>")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
| Status | Description |
|---|---|
202 | Successful Response |
422 | Validation Error |
Customer self-service — load booking details (no auth; signed token)
Return booking details for the holder of a valid signed token.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
token | path | string | true |
Try it
Examples
cURL
curl -X GET 'https://spideriq.ai/api/v1/booking/manage/{token}' \
-H 'Authorization: Bearer <token>'Python
import httpx
resp = httpx.get(
"https://spideriq.ai/api/v1/booking/manage/{token}",
headers={"Authorization": "Bearer <token>"},
)
resp.raise_for_status()
print(resp.json())JavaScript
const resp = await fetch("https://spideriq.ai/api/v1/booking/manage/{token}", {
method: "GET",
headers: { "Authorization": "Bearer <token>" }
});
const data = await resp.json();
console.log(data);Go
package main
import (
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://spideriq.ai/api/v1/booking/manage/{token}", nil)
req.Header.Set("Authorization", "Bearer <token>")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
| Status | Description |
|---|---|
200 | Successful Response |
403 | Token invalid, expired, or tenant mismatch. |
422 | Validation Error |
Customer self-service — reschedule booking (no auth; signed token)
Reschedule a booking. Calls Cal.com, appends a rescheduled row, and sends the customer a follow-up email.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
token | path | string | true |
Try it
Examples
cURL
curl -X POST 'https://spideriq.ai/api/v1/booking/manage/{token}/reschedule' \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{
"new_slot_start": "2026-01-01T00:00:00Z",
"new_slot_end": "2026-01-01T00:00:00Z",
"new_staff_id": "00000000-0000-0000-0000-000000000000"
}'Python
import httpx
resp = httpx.post(
"https://spideriq.ai/api/v1/booking/manage/{token}/reschedule",
headers={"Authorization": "Bearer <token>", "Content-Type": "application/json"},
json={"new_slot_start": "2026-01-01T00:00:00Z", "new_slot_end": "2026-01-01T00:00:00Z", "new_staff_id": "00000000-0000-0000-0000-000000000000"},
)
resp.raise_for_status()
print(resp.json())JavaScript
const resp = await fetch("https://spideriq.ai/api/v1/booking/manage/{token}/reschedule", {
method: "POST",
headers: { "Authorization": "Bearer <token>", "Content-Type": "application/json" },
body: JSON.stringify({"new_slot_start": "2026-01-01T00:00:00Z", "new_slot_end": "2026-01-01T00:00:00Z", "new_staff_id": "00000000-0000-0000-0000-000000000000"})
});
const data = await resp.json();
console.log(data);Go
package main
import (
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{"new_slot_start": "2026-01-01T00:00:00Z", "new_slot_end": "2026-01-01T00:00:00Z", "new_staff_id": "00000000-0000-0000-0000-000000000000"}`)
req, _ := http.NewRequest("POST", "https://spideriq.ai/api/v1/booking/manage/{token}/reschedule", body)
req.Header.Set("Authorization", "Bearer <token>")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
| Status | Description |
|---|---|
200 | Successful Response |
400 | Invalid payload or slot_start in the past. |
403 | Token invalid, expired, or tenant mismatch. |
409 | Booking is already cancelled or in a terminal state. |
503 | Cal.com temporarily unavailable — retry. |
422 | Validation Error |
Customer self-service — cancel booking (no auth; signed token)
Cancel a booking. Calls Cal.com, appends a cancelled row, and sends the customer a confirmation of cancellation.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
token | path | string | true |
Try it
Examples
cURL
curl -X POST 'https://spideriq.ai/api/v1/booking/manage/{token}/cancel' \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{
"reason": "string"
}'Python
import httpx
resp = httpx.post(
"https://spideriq.ai/api/v1/booking/manage/{token}/cancel",
headers={"Authorization": "Bearer <token>", "Content-Type": "application/json"},
json={"reason": "string"},
)
resp.raise_for_status()
print(resp.json())JavaScript
const resp = await fetch("https://spideriq.ai/api/v1/booking/manage/{token}/cancel", {
method: "POST",
headers: { "Authorization": "Bearer <token>", "Content-Type": "application/json" },
body: JSON.stringify({"reason": "string"})
});
const data = await resp.json();
console.log(data);Go
package main
import (
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{"reason": "string"}`)
req, _ := http.NewRequest("POST", "https://spideriq.ai/api/v1/booking/manage/{token}/cancel", body)
req.Header.Set("Authorization", "Bearer <token>")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
| Status | Description |
|---|---|
200 | Successful Response |
403 | Token invalid, expired, or tenant mismatch. |
409 | Booking is already cancelled or in a terminal state. |
503 | Cal.com temporarily unavailable — retry. |
422 | Validation Error |
Get a signed URL to upload a form file_upload field's file
Issue a presigned SeaweedFS PUT URL for one file_upload field.
Validation order: rate limit → flow exists+active → kind='form' → field exists and is file_upload → content-type allowed (skipped when the field has no accept whitelist) → size within the field's max_size_mb → presign.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
flow_id | path | string | true |
Try it
Examples
cURL
curl -X POST 'https://spideriq.ai/api/v1/booking/{flow_id}/upload-presign' \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{
"field_id": "string",
"content_type": "string",
"file_size_bytes": 0,
"filename": "string",
"session_id": "00000000-0000-0000-0000-000000000000"
}'Python
import httpx
resp = httpx.post(
"https://spideriq.ai/api/v1/booking/{flow_id}/upload-presign",
headers={"Authorization": "Bearer <token>", "Content-Type": "application/json"},
json={"field_id": "string", "content_type": "string", "file_size_bytes": 0, "filename": "string", "session_id": "00000000-0000-0000-0000-000000000000"},
)
resp.raise_for_status()
print(resp.json())JavaScript
const resp = await fetch("https://spideriq.ai/api/v1/booking/{flow_id}/upload-presign", {
method: "POST",
headers: { "Authorization": "Bearer <token>", "Content-Type": "application/json" },
body: JSON.stringify({"field_id": "string", "content_type": "string", "file_size_bytes": 0, "filename": "string", "session_id": "00000000-0000-0000-0000-000000000000"})
});
const data = await resp.json();
console.log(data);Go
package main
import (
"net/http"
"strings"
)
func main() {
body := strings.NewReader(`{"field_id": "string", "content_type": "string", "file_size_bytes": 0, "filename": "string", "session_id": "00000000-0000-0000-0000-000000000000"}`)
req, _ := http.NewRequest("POST", "https://spideriq.ai/api/v1/booking/{flow_id}/upload-presign", body)
req.Header.Set("Authorization", "Bearer <token>")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
| Status | Description |
|---|---|
200 | Successful Response |
404 | Flow not found, inactive, or draft. |
422 | Validation failed — non-form flow, unknown / wrong field, content-type or size rejected. |
429 | IP rate limit exceeded. |
503 | File uploads not available for this form. |
Get Feature Status
Try it
Examples
cURL
curl -X GET 'https://spideriq.ai/api/v1/booking/feature-status' \
-H 'Authorization: Bearer <token>'Python
import httpx
resp = httpx.get(
"https://spideriq.ai/api/v1/booking/feature-status",
headers={"Authorization": "Bearer <token>"},
)
resp.raise_for_status()
print(resp.json())JavaScript
const resp = await fetch("https://spideriq.ai/api/v1/booking/feature-status", {
method: "GET",
headers: { "Authorization": "Bearer <token>" }
});
const data = await resp.json();
console.log(data);Go
package main
import (
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://spideriq.ai/api/v1/booking/feature-status", nil)
req.Header.Set("Authorization", "Bearer <token>")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}Responses
| Status | Description |
|---|---|
200 | Successful Response |