{"openapi":"3.1.0","info":{"title":"Virginia Trusted Pros Directory API","version":"1.0.0","description":"Read approved Virginia home-services listings and start homeowner-confirmed quote requests for businesses that opt in."},"servers":[{"url":"https://virginiatrustedpros.com"}],"paths":{"/api/v1/listings":{"get":{"operationId":"searchListings","summary":"Search approved businesses","description":"Featured results appear first and are marked advertising. Other results are alphabetical. Credential verification is separate from listing approval and does not affect standard ordering.","parameters":[{"name":"trade","in":"query","schema":{"type":"string"},"description":"Trade slug"},{"name":"city","in":"query","schema":{"type":"string"},"description":"Virginia city slug"},{"name":"verified","in":"query","schema":{"type":"boolean"},"description":"Only directory-verified credentials"},{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"maximum":1000,"default":1}},{"name":"page_size","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}}],"responses":{"200":{"description":"Approved listings","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListingPage"}}}},"400":{"description":"Invalid filters"},"503":{"description":"Directory database unavailable"}}}},"/api/v1/listings/{slug}":{"get":{"operationId":"getListing","summary":"Read one approved business","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Public business record","content":{"application/json":{"schema":{"type":"object","properties":{"version":{"type":"string"},"listing":{"$ref":"#/components/schemas/Listing"}}}}}},"404":{"description":"No approved listing has that slug"},"503":{"description":"Directory database unavailable"}}}},"/api/v1/quote-requests":{"post":{"operationId":"requestQuote","summary":"Start a homeowner-confirmed quote request","description":"HTTP agents need an issued Bearer key. The browser form uses a Turnstile token instead. Only opted-in businesses accept requests. A 202 response means a confirmation email was sent to the homeowner; the business receives nothing until the homeowner confirms. No quote price is submitted by this operation.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":128}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteRequestInput"}}}},"responses":{"202":{"description":"Homeowner confirmation pending"},"400":{"description":"Invalid input or missing consent"},"401":{"description":"Invalid agent key"},"403":{"description":"Browser challenge failed"},"409":{"description":"Business is not accepting requests"},"429":{"description":"Rate limited"},"503":{"description":"Database or email unavailable"}}}}},"components":{"securitySchemes":{"AgentKey":{"type":"http","scheme":"bearer","description":"Issued to approved agents. Browser forms use a Turnstile token instead."}},"schemas":{"ListingPage":{"type":"object","required":["version","generatedAt","pagination","listings"],"properties":{"version":{"type":"string"},"generatedAt":{"type":"string","format":"date-time"},"filters":{"type":"object"},"pagination":{"type":"object","properties":{"page":{"type":"integer"},"pageSize":{"type":"integer"},"total":{"type":"integer"},"next":{"type":["string","null"],"format":"uri"}}},"listings":{"type":"array","items":{"$ref":"#/components/schemas/Listing"}}}},"Listing":{"type":"object","description":"Approved public fields only. Not a recommendation or proof of insurance.","properties":{"id":{"type":"string","format":"uri"},"slug":{"type":"string"},"name":{"type":"string"},"profileUrl":{"type":"string","format":"uri"},"trade":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"}}},"location":{"type":"object","description":"Street address is omitted when hidden by the owner."},"description":{"type":["string","null"]},"telephone":{"type":["string","null"]},"website":{"type":["string","null"],"format":"uri"},"businessProfile":{"$ref":"#/components/schemas/BusinessProfile","description":"Reviewed owner-provided details on single-listing responses. Omitted from search results."},"credential":{"type":"object","properties":{"status":{"type":"string","enum":["verified","not_verified"]},"authority":{"type":["string","null"]},"type":{"type":["string","null"]},"number":{"type":["string","null"]},"checkedAt":{"type":["string","null"],"format":"date-time"},"lookupUrl":{"type":["string","null"],"format":"uri"}}},"advertising":{"type":"boolean"},"photoUrl":{"type":"null"},"quoteRequests":{"type":"object","properties":{"available":{"type":"boolean"},"endpoint":{"type":["string","null"],"format":"uri"},"confirmation":{"type":["string","null"]}}},"updatedAt":{"type":"string","format":"date-time"}}},"BusinessProfile":{"type":"object","properties":{"headline":{"type":["string","null"]},"specialties":{"type":["string","null"]},"businessHistory":{"type":["string","null"]},"yearEstablished":{"type":["integer","null"]},"bookingUrl":{"type":["string","null"],"format":"uri"},"additionalTrades":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string"},"name":{"type":"string"}}}},"services":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"},"detail":{"type":["string","null"]}}}},"features":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"}}}},"hours":{"type":"array","items":{"$ref":"#/components/schemas/RegularHours"}},"specialHours":{"type":"array","items":{"$ref":"#/components/schemas/SpecialHours"}},"hoursTimezone":{"type":"string","const":"America/New_York"}}},"RegularHours":{"type":"object","properties":{"weekday":{"type":"integer","minimum":0,"maximum":6},"mode":{"type":"string","enum":["open","closed","all_day"]},"opensAt":{"type":["string","null"]},"closesAt":{"type":["string","null"]}}},"SpecialHours":{"type":"object","properties":{"onDate":{"type":"string","format":"date"},"mode":{"type":"string","enum":["open","closed","all_day"]},"opensAt":{"type":["string","null"]},"closesAt":{"type":["string","null"]}}},"QuoteRequestInput":{"type":"object","additionalProperties":false,"required":["listingSlug","requesterName","requesterEmail","projectSummary","postalCode","timing","homeownerConsent"],"properties":{"listingSlug":{"type":"string"},"requesterName":{"type":"string","minLength":2,"maxLength":100},"requesterEmail":{"type":"string","format":"email"},"projectSummary":{"type":"string","minLength":20,"maxLength":2000},"postalCode":{"type":"string","pattern":"^[0-9]{5}$"},"timing":{"type":"string","enum":["asap","within_month","flexible"]},"agentName":{"type":"string","maxLength":100},"turnstileToken":{"type":"string","description":"Browser form only. HTTP agents use the Bearer key."},"homeownerConsent":{"const":true,"description":"Caller has the homeowner's permission. The homeowner must still confirm by email."}}}}}}