SSpiderIQ
SSpiderIQ

Docs / api-reference/spiderbook

SpiderBook

9 endpoints from the published OpenAPI import.

POST/api/v1/booking/{flow_id}/submit

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 to public.results with worker_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 422 submit_not_applicable_for_funnel envelope. To collect data from a funnel visitor, embed a form sub-flow whose own /submit persists 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

NameInTypeRequiredDescription
flow_idpathstringtrue
Try it
Query

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

StatusDescription
200Successful Response
400Consent missing, invalid payload, or unknown / missing required form field.
403Turnstile verification failed.
404Flow not found or inactive.
409Slot hold expired or Cal.com reported slot taken.
422Body fails BookingAnswers validation (kind='booking' / 'commerce' only) OR kind='funnel' submit is not applicable.
429IP rate limit exceeded.
503Cal.com temporarily unavailable — retry.
GET/api/v1/booking/{flow_id}

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

NameInTypeRequiredDescription
flow_idpathstringtrue
Try it
Query

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

StatusDescription
200Successful Response
404Flow not found (RESOURCE_NOT_FOUND envelope)
409Flow is not a booking (WRONG_FLOW_KIND envelope)
422Validation Error
GET/api/v1/booking/{flow_id}/render

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

NameInTypeRequiredDescription
flow_idpathstringtrue
Try it
Query

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

StatusDescription
200Successful Response
422Validation Error
POST/api/v1/booking/{flow_id}/render

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

NameInTypeRequiredDescription
flow_idpathstringtrue
Try it
Query

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

StatusDescription
202Successful Response
422Validation Error
GET/api/v1/booking/manage/{token}

Customer self-service — load booking details (no auth; signed token)

Return booking details for the holder of a valid signed token.

Parameters

NameInTypeRequiredDescription
tokenpathstringtrue
Try it
Query

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

StatusDescription
200Successful Response
403Token invalid, expired, or tenant mismatch.
422Validation Error
POST/api/v1/booking/manage/{token}/reschedule

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

NameInTypeRequiredDescription
tokenpathstringtrue
Try it
Query

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

StatusDescription
200Successful Response
400Invalid payload or slot_start in the past.
403Token invalid, expired, or tenant mismatch.
409Booking is already cancelled or in a terminal state.
503Cal.com temporarily unavailable — retry.
422Validation Error
POST/api/v1/booking/manage/{token}/cancel

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

NameInTypeRequiredDescription
tokenpathstringtrue
Try it
Query

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

StatusDescription
200Successful Response
403Token invalid, expired, or tenant mismatch.
409Booking is already cancelled or in a terminal state.
503Cal.com temporarily unavailable — retry.
422Validation Error
POST/api/v1/booking/{flow_id}/upload-presign

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

NameInTypeRequiredDescription
flow_idpathstringtrue
Try it
Query

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

StatusDescription
200Successful Response
404Flow not found, inactive, or draft.
422Validation failed — non-form flow, unknown / wrong field, content-type or size rejected.
429IP rate limit exceeded.
503File uploads not available for this form.
GET/api/v1/booking/feature-status

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

StatusDescription
200Successful Response