STURLASON – BOOK DIRECTLY OVER HTTP (2026-10-20, v1.2) No customer setup: no account, customer API key, connector installation, SDK, MCP connection, browser, JavaScript, cookies or session handshake is required. The agent must already have permission and tools to issue HTTPS requests. A customer's normal approval and one email verification code are required to submit a real booking. Do not equate no setup with no customer consent. ONE ENDPOINT https://www.sturlason.info/agent-booking/public/tools.php GET with no parameters explains usage and tool schemas. POST with Content-Type: application/json calls any of the same 10 tools. POST body is always {"tool":"TOOL_NAME","arguments":{...}}. No Authorization header or MCP session header is needed. DISCOVERY AND REFERENCE https://www.sturlason.info/llms.txt https://www.sturlason.info/ai-booking.json https://www.sturlason.info/agent-booking/public/openapi.json https://www.sturlason.info/agent-booking/public/capabilities.json llms.txt is a published convention and ai-booking.json is our own manifest; neither guarantees automatic discovery, ranking or support by any AI provider. LIVE READS USING ONLY GET Services and requirements: https://www.sturlason.info/agent-booking/public/tools.php?tool=list_services Upcoming Express CV times in Oslo: https://www.sturlason.info/agent-booking/public/tools.php?tool=find_slots&studio=oslo&service=cv_private To search a week, append &from=YYYY-MM-DD&to=YYYY-MM-DD with real ISO dates. For example, 26–30 October 2026: https://www.sturlason.info/agent-booking/public/tools.php?tool=find_slots&studio=oslo&service=cv_private&from=2026-10-26&to=2026-10-30 Dates refer to Europe/Oslo. Use startsAt with its returned timezone offset. Check asOf and moreAvailable. An omitted date or truncated result does not prove that every other date is unavailable. Never present a stale fetched copy as live. GET is read-only: no quote, hold, contact code, submission, change or cancellation. Never put names, email addresses, phone numbers, notes, codes or booking tokens in URLs. GET rejects unknown parameters and all write tools. Structured public passPeople/familyChildAges choices can be passed as URL-encoded JSON arrays; POST is simpler for these. POST arguments must go in its JSON body, not the URL. EXACT POST EXAMPLES All examples go to the ONE ENDPOINT above. Replace every REPLACE_... value with real values obtained from the service or the customer. Examples are not an instruction to execute a booking. Do not invent contact details or approval. 1. Get services/required choices: {"tool":"list_services","arguments":{}} 2. Find slots (add from/to ISO dates when the customer specified a period): {"tool":"find_slots","arguments":{"selection":{"studio":"oslo","service":"cv_private"}}} 3. Quote the chosen actual slot, without reserving: {"tool":"quote_booking","arguments":{"selection":{"studio":"oslo","service":"cv_private","slotId":"REPLACE_SLOT_ID_FROM_SEARCH"},"agent":"other"}} Use agent=claude, chatgpt, codex or other as the agent's declared source. The service returns bookingToken, quote.priceMinor, currency, startsAt, durationMinutes and expiresAt. Tokens are private access capabilities and must be kept securely by the agent. Do not place them in shareable material. Quote prices include supported selected options and the selected time's rules. Never assume every service costs 750 NOK; use the exact returned priceMinor. 4. Show service, exact date/time, duration and total price to the customer. After their authorization, hold only the selected time: {"tool":"hold_slot","arguments":{"bookingToken":"REPLACE_PRIVATE_BOOKING_TOKEN"}} The deadline is no later than 10 minutes after the quote was created, not a new 10 minutes for each step. If expired before submission, inspect status first; then obtain a fresh quote only when no booking is being submitted or registered. 5. With contact data provided/authorized by the customer, send the email code: {"tool":"request_contact_code","arguments":{"bookingToken":"REPLACE_PRIVATE_BOOKING_TOKEN","contact":{"name":"REPLACE_CUSTOMER_NAME","email":"customer@example.invalid","phone":"REPLACE_CUSTOMER_PHONE"}}} Replace the .invalid example address. Ask the customer for the received code, or use mailbox access they explicitly authorized. Never read admin storage to bypass contact verification. There is no separate registration or API key. 6. Only after customer approval and their correct code, submit: {"tool":"submit_booking","arguments":{"bookingToken":"REPLACE_PRIVATE_BOOKING_TOKEN","code":"REPLACE_CUSTOMER_EMAIL_CODE","customerApproved":true,"acceptedPriceMinor":75000}} 75000 is an example for a quote of 750 NOK. Use EXACTLY the approved quote's priceMinor; never force this example price. Submission creates a real booking request and can send email/SMS. marketingConsent is not requested by this API. 7. Retrieve status: {"tool":"get_booking","arguments":{"bookingToken":"REPLACE_PRIVATE_BOOKING_TOKEN"}} requested means the request was registered; it is not separate studio approval. Do not claim a reservation or successful booking from a time list, quote, contact email or an uncertain response. Preserve the returned reference. 8. Release an unsubmitted hold when the customer stops: {"tool":"release_hold","arguments":{"bookingToken":"REPLACE_PRIVATE_BOOKING_TOKEN"}} 9. Cancel only a capability-owned booking the customer authorized cancelling: {"tool":"cancel_booking","arguments":{"bookingToken":"REPLACE_PRIVATE_BOOKING_TOKEN","customerApproved":true}} 10. Move only after explicit approval; quote and hold a new slot first: {"tool":"move_booking","arguments":{"bookingToken":"REPLACE_ORIGINAL_PRIVATE_TOKEN","newQuoteToken":"REPLACE_NEW_HELD_QUOTE_TOKEN","customerApproved":true,"acceptedPriceMinor":75000}} Moving requires identical service, studio, options and price. Other changes and waitlists are handled by the studio/human form. Company contracts, volume and employer invoicing are not anonymous tool services. PASSPORTS / ID / VISA Use service=pass and passPeople (1–4 people). Each person's required choices: nationality = intended country/photo standard, not inferred citizenship; documentType = pass|visa|other; package = print|digital|print_digital; extraSets = array of {"nationality":"...","documentType":"..."}. Example choice after the CUSTOMER selected this country/standard and delivery: {"tool":"find_slots","arguments":{"selection":{"studio":"oslo","service":"pass","passPeople":[{"nationality":"EU-standard","documentType":"pass","package":"print_digital","extraSets":[]}]}}} Use the same choices plus the actual slotId for quote_booking. Individual people may have different choices. Do not guarantee acceptance by an issuing authority. ERRORS AND RETRIES 400: invalid input; errors may include the exact tool inputSchema. Correct the request without exposing contact data. 405: GET is public-read-only. 409: inspect error (expired, busy, quote_changed, unsupported_change). 429: wait/back off; do not bypass limits with new identities. 503/network timeout during submission: KEEP the same bookingToken. Retrieve status or retry submit_booking only with that same token, never a new quote. reconciliation_required: stop automatic submission; studio review is needed. Do not turn uncertain submission into a second appointment. Keep capability tokens in POST bodies and secure internal storage only. Price and availability are rechecked. Reading endpoints never creates a hold. HTTP results use Cache-Control: no-store and freshness timestamps on slot lists. CONTACT / EXISTING WEBSITE fotograf@sturlason.info / +47 22 42 41 22 Privacy: https://www.sturlason.info/personvern.html Human booking remains https://www.sturlason.info/booking.html with unchanged design. MCP is optional: https://www.sturlason.info/agent-booking/public/mcp.php Direct HTTP uses the same booking service and requires no MCP configuration.