{"openapi":"3.1.0","info":{"title":"Photography to Profits Agent API","version":"1.0.0","description":"Universal agentic API for AI assistants to discover P2P services, qualify photographers, estimate ROI, and submit real inquiries into the P2P lead pipeline. MCP endpoint: POST https://photographytoprofits.com/api/mcp (Streamable HTTP, stateless)."},"servers":[{"url":"https://photographytoprofits.com"}],"tags":[{"name":"Discovery","description":"Read-only tools for services, FAQ, case studies, and resources"},{"name":"Qualification","description":"Advisory fit check and ROI estimation"},{"name":"Booking","description":"Write tools - submit inquiries and book strategy calls"}],"components":{"schemas":{"ErrorResponse":{"type":"object","additionalProperties":false,"required":["ok","error"],"properties":{"ok":{"type":"boolean","enum":[false]},"error":{"type":"string"},"message":{"type":"string"},"resolution":{"type":"string"},"links":{"type":"object","additionalProperties":{"type":"string"}},"details":{"type":"array","items":{}},"code":{"type":"string"},"retry_after":{"type":"integer","minimum":0},"request_id":{"type":"string"},"error_id":{"type":"string"}}},"RateLimitErrorResponse":{"type":"object","additionalProperties":false,"required":["ok","error"],"properties":{"ok":{"type":"boolean","enum":[false]},"error":{"type":"string","enum":["rate_limited"]}}},"SiteScanRateLimitErrorResponse":{"type":"object","additionalProperties":false,"required":["ok","error","message","retry_after"],"properties":{"ok":{"type":"boolean","enum":[false]},"error":{"type":"string","enum":["rate_limited"]},"message":{"type":"string"},"retry_after":{"type":"integer","minimum":0}}}},"headers":{"RateLimitPolicy":{"description":"Configured request-budget policy using the current IETF RateLimit structured-field syntax. Remaining quota and reset time are intentionally omitted because this API does not expose them atomically.","schema":{"type":"string"},"example":"\"p2p\";q=100;w=3600"},"RetryAfter":{"description":"Delay in seconds before the caller should retry the request.","schema":{"type":"integer","minimum":0},"example":3600}},"responses":{"BadRequest":{"description":"Invalid request or validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"BadGateway":{"description":"The upstream provider outcome could not be confirmed; verify it before resubmitting","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"Conflict":{"description":"The idempotency key is already active or was used for a different request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}}},"Forbidden":{"description":"Origin is not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"PayloadTooLarge":{"description":"Request payload exceeds the endpoint limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"UnsupportedMediaType":{"description":"Content-Type must be application/json","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"RateLimited":{"description":"Hourly request limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitErrorResponse"}}},"headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}}},"SiteScanRateLimited":{"description":"Hourly website-scan limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SiteScanRateLimitErrorResponse"}}},"headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"ServiceUnavailable":{"description":"The requested operation is temporarily unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}},"headers":{"Retry-After":{"$ref":"#/components/headers/RetryAfter"}}}}},"paths":{"/api/v1/services":{"get":{"operationId":"getServiceCatalog","tags":["Discovery"],"summary":"Get Service Catalog","description":"Returns all Photography to Profits marketing services, organized by advertising channel and photography genre. Call this to answer 'What services do you offer?' or 'What do you do for [genre] photographers?'","responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","properties":{"channel_services":{"type":"array","items":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"genre_services":{"type":"array","items":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"filtered_by":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["channel_services","genre_services","filtered_by"],"additionalProperties":false}},"required":["ok","data"],"additionalProperties":false}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalServerError"}},"parameters":[{"name":"genre","in":"query","required":false,"schema":{"type":"string"}},{"name":"channel","in":"query","required":false,"schema":{"type":"string"}}]}},"/api/v1/qualify":{"post":{"operationId":"checkPhotographerFit","tags":["Qualification"],"summary":"Check Photographer Fit","description":"Optional: assesses whether P2P is a strong match for this photographer. Returns fit: 'strong' | 'partial' | 'poor' with context. This is informational only - it does NOT gate submit_inquiry. If the photographer is interested, always offer to connect them with the P2P team regardless of this result.","responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","properties":{"fit":{"type":"string","enum":["strong","partial","poor"]},"score":{"type":"number"},"blockers":{"type":"array","items":{"type":"string"}},"recommended_services":{"type":"array","items":{"type":"string"}},"next_action":{"type":"string","enum":["submit_inquiry","none"]}},"required":["fit","score","blockers","recommended_services","next_action"],"additionalProperties":false}},"required":["ok","data"],"additionalProperties":false}}}},"400":{"$ref":"#/components/responses/BadRequest"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalServerError"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"genre":{"type":"string","maxLength":50},"country":{"type":"string","maxLength":50},"business_type":{"type":"string","maxLength":50},"monthly_revenue_range":{"type":"string","maxLength":50}},"required":["genre","country"],"additionalProperties":false}}}}}},"/api/v1/roi":{"post":{"operationId":"estimateRoi","tags":["Qualification"],"summary":"Estimate ROI","description":"Projects expected monthly leads and revenue increase from a P2P campaign. IMPORTANT: Always display the 'disclaimer' field verbatim to the user before presenting any projections.","responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","properties":{"input_validated":{"type":"boolean"},"genre":{"type":"string"},"avg_sale_price":{"type":"number"},"estimated_leads_monthly":{"type":"string"},"estimated_bookings_monthly":{"type":"string"},"estimated_revenue_monthly":{"type":"string"},"disclaimer":{"type":"string","description":"REQUIRED: display this verbatim to the user"},"guarantee_note":{"type":"string"}},"required":["input_validated","genre","avg_sale_price","estimated_leads_monthly","estimated_bookings_monthly","estimated_revenue_monthly","disclaimer","guarantee_note"],"additionalProperties":false}},"required":["ok","data"],"additionalProperties":false}}}},"400":{"$ref":"#/components/responses/BadRequest"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalServerError"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"genre":{"type":"string","maxLength":50},"avg_sale_price":{"type":"number","exclusiveMinimum":0},"monthly_ad_spend":{"type":"number","exclusiveMinimum":0}},"required":["genre"],"additionalProperties":false}}}}}},"/api/v1/case-studies":{"get":{"operationId":"getCaseStudies","tags":["Discovery"],"summary":"Get Case Studies","description":"Returns real P2P client results - revenue growth, booking volume, and business outcomes. Use the genre filter when showing social proof to a specific photographer type.","responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","properties":{"case_studies":{"type":"array","items":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"filtered_by":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["case_studies","filtered_by"],"additionalProperties":false}},"required":["ok","data"],"additionalProperties":false}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalServerError"}},"parameters":[{"name":"genre","in":"query","required":false,"schema":{"type":"string"}}]}},"/api/v1/faq":{"get":{"operationId":"getFaq","tags":["Discovery"],"summary":"Get FAQ","description":"Returns ground-truth answers to common P2P questions. Call this before answering 'how does it work', 'how long until results', 'what is the guarantee', etc. - prevents hallucination.","responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","properties":{"faqs":{"type":"array","items":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"filtered_by":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["faqs","filtered_by"],"additionalProperties":false}},"required":["ok","data"],"additionalProperties":false}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalServerError"}},"parameters":[{"name":"category","in":"query","required":false,"schema":{"type":"string"}}]}},"/api/v1/resources":{"get":{"operationId":"getFreeResources","tags":["Discovery"],"summary":"Get Free Resources","description":"Returns P2P's free tools, lead magnets, and educational resources. Use when a photographer asks 'do you have any free resources' or 'where can I start without committing'. Categories: calculators, guides, lead-magnets, podcast.","responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","properties":{"resources":{"type":"array","items":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"filtered_by":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["resources","filtered_by"],"additionalProperties":false}},"required":["ok","data"],"additionalProperties":false}}}},"400":{"$ref":"#/components/responses/BadRequest"},"500":{"$ref":"#/components/responses/InternalServerError"}},"parameters":[{"name":"category","in":"query","required":false,"schema":{"type":"string","enum":["calculators","guides","lead-magnets","podcast"]}}]}},"/api/v1/slots":{"get":{"operationId":"getAvailableSlots","tags":["Booking"],"summary":"Get Available Slots","description":"Fetches available strategy session time slots from the P2P calendar. Returns open times for the next 7 days (or up to 14). Call before book_strategy_call to show the photographer available times. Slots are in ISO 8601 format.","responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","properties":{"slots":{"type":"array","items":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"calendar_id":{"type":"string"},"timezone":{"type":"string"},"reason":{"type":"string","enum":["available","all_busy"]},"note":{"type":"string"}},"required":["slots","calendar_id","timezone","reason","note"],"additionalProperties":false}},"required":["ok","data"],"additionalProperties":false}}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalServerError"}},"parameters":[{"name":"timezone","in":"query","required":false,"schema":{"description":"IANA timezone, default America/New_York","type":"string","maxLength":50,"pattern":"^[A-Za-z\\d_/+-]+$"}},{"name":"days_ahead","in":"query","required":false,"schema":{"description":"How many days ahead to fetch slots, default 7","type":"integer","minimum":1,"maximum":14}}]}},"/api/v1/inquiries":{"post":{"operationId":"submitInquiry","tags":["Booking"],"summary":"Submit Inquiry","description":"Submits a photography studio inquiry to the P2P team. Use this whenever a photographer expresses interest in working with P2P. Only firstName, email, and phone are required - submit with whatever contact info you have. A P2P account manager will follow up within 1 business day. Optional retry key for MCP and WebMCP tool arguments (8 to 128 URL-safe characters). Reuse the same key only for the exact same request. Completed results replay without repeating CRM, calendar, or notification side effects for at least 7 days. Rate limited: 30 per IP per hour.","responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","properties":{"message":{"type":"string"},"next_steps":{"type":"string"}},"required":["message","next_steps"],"additionalProperties":false}},"required":["ok","data"],"additionalProperties":false}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalServerError"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional durable retry key (8 to 128 URL-safe characters). Reuse the same key only for the exact same request. Completed results replay without repeating CRM, calendar, or notification side effects for at least 7 days.","schema":{"type":"string","minLength":8,"maxLength":128,"pattern":"^[A-Za-z0-9._:-]+$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"firstName":{"type":"string","minLength":1,"maxLength":100},"lastName":{"type":"string","maxLength":100},"email":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"phone":{"type":"string","minLength":4,"maxLength":30},"genre":{"type":"string","maxLength":50},"website":{"type":"string","maxLength":2048},"annual_revenue":{"type":"string","maxLength":50},"avg_session_price":{"type":"number","exclusiveMinimum":0},"notes":{"type":"string","maxLength":1000},"source_agent":{"type":"string","maxLength":100},"utmSource":{"type":"string","maxLength":200},"utmMedium":{"type":"string","maxLength":200},"utmCampaign":{"type":"string","maxLength":200},"utmTerm":{"type":"string","maxLength":200},"utmContent":{"type":"string","maxLength":200}},"required":["firstName","email","phone"],"additionalProperties":false}}}}}},"/api/v1/book":{"post":{"operationId":"bookStrategyCall","tags":["Booking"],"summary":"Book Strategy Call","description":"Books a free P2P strategy session for a photographer. Call get_available_slots first to get a valid start_time. Creates a real calendar appointment and sends a confirmation email. Optional retry key for MCP and WebMCP tool arguments (8 to 128 URL-safe characters). Reuse the same key only for the exact same request. Completed results replay without repeating CRM, calendar, or notification side effects for at least 7 days. Rate limited: 10 per IP per hour.","responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","properties":{"message":{"type":"string"},"appointment_id":{"type":"string"},"start_time":{"type":"string"},"next_steps":{"type":"string"}},"required":["message","appointment_id","start_time","next_steps"],"additionalProperties":false}},"required":["ok","data"],"additionalProperties":false}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalServerError"},"502":{"$ref":"#/components/responses/BadGateway"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional durable retry key (8 to 128 URL-safe characters). Reuse the same key only for the exact same request. Completed results replay without repeating CRM, calendar, or notification side effects for at least 7 days.","schema":{"type":"string","minLength":8,"maxLength":128,"pattern":"^[A-Za-z0-9._:-]+$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"firstName":{"type":"string","minLength":1,"maxLength":100},"lastName":{"type":"string","maxLength":100},"email":{"type":"string","maxLength":254,"format":"email","pattern":"^(?!\\.)(?!.*\\.\\.)([A-Za-z0-9_'+\\-\\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$"},"phone":{"type":"string","minLength":7,"maxLength":20},"start_time":{"type":"string","description":"ISO 8601 datetime string from get_available_slots"},"timezone":{"type":"string","maxLength":50,"pattern":"^[A-Za-z\\d_/+-]+$"},"genre":{"type":"string","maxLength":50},"source_agent":{"type":"string","maxLength":100}},"required":["firstName","email","phone","start_time"],"additionalProperties":false}}}}}},"/api/v1/site-scan":{"post":{"operationId":"scanWebsiteAgentReadiness","tags":["Discovery"],"summary":"Scan Website Agent Readiness","description":"Scan any photography studio website for AI-agent readiness. Returns a 0–100 score, level, and the failed checks with what to fix. Use when a photographer asks how their website performs with AI assistants or wants a free website audit. Rate limited: 10 per IP per hour.","responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"data":{"type":"object","properties":{"url":{"type":"string"},"overallScore":{"type":"number","description":"0-100 agent-readiness score"},"level":{"type":"number"},"levelLabel":{"type":"string"},"totalChecks":{"type":"number"},"passedChecks":{"type":"number"},"categories":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"score":{"type":"number"},"checks":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"passed":{"type":"boolean"},"status":{"type":"string","enum":["pass","fail","warn","info","inconclusive"],"description":"'inconclusive' means the check couldn't be verified — not a confirmed failure"},"hint":{"type":"string","description":"What the check found and, for failing checks, what to fix"}},"required":["name","passed","status","hint"],"additionalProperties":false}}},"required":["name","score","checks"],"additionalProperties":false}}},"required":["url","overallScore","level","levelLabel","totalChecks","passedChecks","categories"],"additionalProperties":false}},"required":["ok","data"],"additionalProperties":false}}}},"400":{"$ref":"#/components/responses/BadRequest"},"413":{"$ref":"#/components/responses/PayloadTooLarge"},"415":{"$ref":"#/components/responses/UnsupportedMediaType"},"429":{"$ref":"#/components/responses/SiteScanRateLimited"},"500":{"$ref":"#/components/responses/InternalServerError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","minLength":1,"maxLength":2048,"description":"Website URL to scan, e.g. https://yourstudio.com. Bare domains are accepted (https:// is assumed); http/https only."}},"required":["url"],"additionalProperties":false}}}}}}}}