{"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"},{"name":"Identity","description":"Whoami / credential introspection for connectors and SDKs"}],"paths":{"/api/v1/leads":{"post":{"tags":["Booking"],"summary":"Submit a marketing-site lead into a workspace's CRM. Requires a per-workspace lead-intake API key (wlk_ server-side or wpk_ browser publishable) with the lead:write capability; the workspace is derived from the key.","operationId":"submitWorkspaceLead","security":[{"workspaceLeadIntakeKey":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string"},"description":"Client-supplied dedupe key (or meta.externalId) — makes retries safe."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["lead"],"properties":{"lead":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"},"firstName":{"type":"string"},"lastName":{"type":"string"},"name":{"type":"string","description":"Optional alternative to firstName/lastName."},"phone":{"type":"string"},"company":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"customFields":{"type":"object","additionalProperties":true,"description":"Arbitrary client fields (utm_*, budget, …) — forward-compatible, never needs a contract change."}}},"source":{"type":"object","properties":{"type":{"type":"string"},"name":{"type":"string"},"provider":{"type":"string"},"origin":{"type":"string"}}},"meta":{"type":"object","properties":{"externalId":{"type":"string","description":"Provider submission id (dedupe)."},"ip":{"type":"string"},"userAgent":{"type":"string"},"fbp":{"type":"string","description":"Meta browser id, forwarded for CAPI match."},"fbc":{"type":"string","description":"Meta click id, forwarded for CAPI match."},"eventId":{"type":"string","description":"Shared id for browser↔server dedup."}}}}}}}},"responses":{"201":{"description":"Lead created in the workspace CRM"},"401":{"description":"Missing or invalid API key"},"403":{"description":"Origin not allowed, or key lacks lead:write"},"413":{"description":"Request body exceeds the size cap"},"429":{"description":"Rate limit exceeded"}}}},"/api/v1/lead-import/{id}":{"post":{"tags":["Booking"],"summary":"Import a lead from an outside website form into the workspace CRM. The opaque li_ source token in the path authenticates the import source; HMAC sources also require signature headers.","operationId":"importExternalFormLead","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^li_[A-Za-z0-9_-]{20,}$"},"description":"Opaque lead-import source token generated in the website forms admin surface."},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":160},"description":"Stable provider submission id. Required when external_submission_id is absent."},{"name":"X-P2P-Timestamp","in":"header","required":false,"schema":{"type":"string"},"description":"Required only for HMAC import sources."},{"name":"X-P2P-Signature","in":"header","required":false,"schema":{"type":"string"},"description":"Required only for HMAC import sources."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["contact","fields","consent","provider"],"properties":{"external_submission_id":{"type":"string"},"external_form_id":{"type":"string"},"submitted_at":{"type":"string","format":"date-time"},"contact":{"type":"object","properties":{"email":{"type":"string","format":"email"},"phone":{"type":"string"},"first_name":{"type":"string"},"last_name":{"type":"string"},"full_name":{"type":"string"},"website":{"type":"string"}}},"fields":{"type":"object","additionalProperties":true,"description":"Provider field payload. Field mappings can promote values into contact fields."},"consent":{"type":"object","properties":{"email":{"type":"boolean"},"sms":{"type":"boolean"},"text":{"type":"string"}}},"attribution":{"type":"object","additionalProperties":true},"provider":{"type":"object","properties":{"name":{"type":"string"},"raw":{"type":"object","additionalProperties":true}}}}}}}},"responses":{"200":{"description":"Duplicate idempotent submission acknowledged"},"201":{"description":"Lead imported into the capture-form submission pipeline"},"400":{"description":"Invalid payload or missing idempotency/identity data"},"401":{"description":"Invalid source token or HMAC signature"},"403":{"description":"Source IP rejected by allowlist"},"413":{"description":"Request body exceeds the size cap"},"429":{"description":"Rate limit exceeded"}}}},"/api/v1/chat-widget/config":{"get":{"tags":["Booking"],"summary":"Load public chat widget settings for a publishable workspace key. Requires a browser-safe wpk_ key and an Origin that exactly matches the key's allowed origins.","operationId":"getChatWidgetConfig","security":[{"workspaceLeadIntakeKey":[]}],"responses":{"200":{"description":"The public chat widget display and behavior config","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"config":{"type":"object","properties":{"enabled":{"type":"boolean"},"greetingText":{"type":"string"},"confirmationText":{"type":"string"},"brandColor":{"type":"string"},"position":{"type":"string","enum":["bottom-right","bottom-left"]},"collectPhone":{"type":"boolean"},"collectMessage":{"type":"boolean"},"targetTags":{"type":"array","items":{"type":"string"}},"openDelayMs":{"type":"integer","minimum":0,"maximum":120000},"autoOpen":{"type":"boolean"},"showFrequency":{"type":"string","enum":["always","once_per_session","once_per_day","once_ever"]},"mobileBehavior":{"type":"string","enum":["bottom-sheet","bubble","disabled"]},"consentText":{"type":"string"},"privacyUrl":{"type":"string"}}}}}}}}}},"401":{"description":"Missing or invalid publishable API key"},"403":{"description":"Origin not allowed, secret key used in browser, or key lacks lead:write"},"429":{"description":"Rate limit exceeded"}}}},"/api/v1/me":{"get":{"tags":["Identity"],"summary":"Whoami — confirm a credential is valid and learn its identity + capability surface. Works with BOTH a workspace API key (wlk_/wpk_) and an OAuth/MCP access token. The connector handshake (Zapier Test Connection, SDK bootstrap).","operationId":"getAuthPrincipal","security":[{"workspaceLeadIntakeKey":[]},{"workspaceOAuth":[]}],"responses":{"200":{"description":"The authenticated principal","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"data":{"type":"object","properties":{"workspaceId":{"type":["string","null"],"description":"Tenant boundary derived from the credential. Null for an agency-scoped OAuth token."},"principalKind":{"type":"string","enum":["workspace_key","oauth_workspace","oauth_agency"]},"credentialFamily":{"type":"string","enum":["api_key","oauth"]},"scopes":{"type":"array","items":{"type":"string"}},"keyType":{"type":"string","enum":["secret","publishable"],"description":"Present only for API keys."},"agencyId":{"type":["string","null"],"description":"Present only for an agency OAuth token."},"expiresAt":{"type":"string","format":"date-time"}}}}}}}},"401":{"description":"Missing or invalid credential (does not reveal which check failed)"},"429":{"description":"Rate limit exceeded"}}}},"/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}}}}},"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"},"reasons":{"type":"array","items":{"type":"string"}},"blockers":{"type":"array","items":{"type":"string"}},"recommended_services":{"type":"array","items":{"type":"string"}},"next_action":{"type":"string","enum":["submit_inquiry","none"]}},"required":["fit","score","reasons","blockers","recommended_services","next_action"],"additionalProperties":false}},"required":["ok","data"],"additionalProperties":false}}}}},"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}}}}},"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}}}}},"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}}}}},"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}}}}},"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":{}}},"timezone":{"type":"string"}},"required":["slots","timezone"],"additionalProperties":false}},"required":["ok","data"],"additionalProperties":false}}}},"400":{"description":"Validation error (invalid timezone or days_ahead)"}},"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. Rate limited: 5 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"},"contact_id":{"type":"string"}},"required":["message","contact_id"],"additionalProperties":false}},"required":["ok","data"],"additionalProperties":false}}}},"400":{"description":"Validation error - check required fields (firstName, email, phone)"},"413":{"description":"Payload too large (max 8KB)"},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[false]},"error":{"type":"string","enum":["rate_limited"]},"retry_after":{"type":"integer","description":"Seconds until retry is allowed"}}}}}}},"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. Rate limited: 5 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"}},"required":["message","appointment_id"],"additionalProperties":false}},"required":["ok","data"],"additionalProperties":false}}}},"400":{"description":"Validation error or booking failure"},"413":{"description":"Payload too large"},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean","enum":[false]},"error":{"type":"string","enum":["rate_limited"]},"retry_after":{"type":"integer","description":"Seconds until retry is allowed"}}}}}}},"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}}}}}}},"components":{"securitySchemes":{"workspaceLeadIntakeKey":{"type":"http","scheme":"bearer","description":"Per-workspace lead-intake API key with the lead:write capability. wlk_ keys are server-side only; wpk_ keys are browser-safe publishable keys that must be locked to exact allowed origins."},"workspaceOAuth":{"type":"http","scheme":"bearer","description":"OAuth 2.1 / MCP access token (workspace- or agency-scoped). Issued via the external-agent OAuth authorization server; carries canonical scopes (e.g. workspace:read)."}}}}