{"openapi":"3.1.0","info":{"title":"LeyApp Public API","version":"1.0.0","description":"Search verified immigration lawyers across Spain, check availability, and start bookings. Bookings are never charged via the API: every booking is confirmed and paid by the client on leyapp.es. Anonymous access is allowed at low rate limits; API keys (create one at /dashboard/developer) raise limits and unlock keyed endpoints. Test keys (leyk_test_) run the full validation path but create nothing.","contact":{"url":"https://leyapp.es/developers"}},"servers":[{"url":"https://leyapp.es/api/v1"}],"security":[{"apiKey":[]},{}],"tags":[{"name":"lawyers","description":"Search and public profiles"},{"name":"availability","description":"Consultation slots"},{"name":"bookings","description":"Booking intents (user confirms on-site)"},{"name":"reference","description":"Practice areas and cities"}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"API key from Dashboard → Developer: \"Authorization: Bearer leyk_…\" (live) or leyk_test_… (test). MCP tokens (ley_…) are not valid here."}},"schemas":{"Problem":{"type":"object","description":"RFC 9457 Problem Details error","properties":{"type":{"type":"string","description":"Documentation URL for this error code"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"code":{"type":"string","description":"Stable machine-readable code: invalid_request | authentication_required | invalid_api_key | expired_api_key | insufficient_scope | rate_limited | idempotency_key_reused | not_found"},"instance":{"type":"string","description":"X-Request-Id of this request — quote it to support"}}},"LawyerSummary":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"city":{"type":"string"},"practice_areas":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"slug":{"type":"string"},"is_primary":{"type":"boolean"}}}},"languages":{"type":"array","items":{"type":"string"}},"consultation_fee_cents":{"type":"integer","description":"Lawyer's base fee in EUR cents"},"currency":{"type":"string","example":"EUR"},"platform_commission_percent":{"type":"integer","example":15},"average_rating":{"type":["number","null"]},"total_bookings":{"type":["integer","null"]},"next_available":{"type":["string","null"],"format":"date"},"profile_url":{"type":"string","format":"uri"},"booking_url":{"type":"string","format":"uri"}}},"Slot":{"type":"object","properties":{"date":{"type":"string","format":"date"},"start_time":{"type":"string","example":"09:00:00"},"end_time":{"type":"string","example":"10:00:00"}}}}},"paths":{"/lawyers/search":{"get":{"tags":["lawyers"],"operationId":"searchLawyers","summary":"Search verified lawyers","description":"Same search engine as leyapp.es. Verified, publicly listed lawyers only. Scope: lawyers:read (anonymous allowed).","parameters":[{"name":"city","in":"query","schema":{"type":"string"},"example":"Barcelona"},{"name":"practice_area","in":"query","schema":{"type":"string"},"description":"Slug from GET /practice-areas","example":"immigration"},{"name":"language","in":"query","schema":{"type":"string"},"example":"en"},{"name":"min_fee_eur","in":"query","schema":{"type":"number"}},{"name":"max_fee_eur","in":"query","schema":{"type":"number"}},{"name":"sort","in":"query","schema":{"type":"string","enum":["rating","price_asc","price_desc","reviews"]}},{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":25,"default":10}}],"responses":{"200":{"description":"Paginated results","headers":{"X-Request-Id":{"description":"Unique id of this request — include it in support inquiries","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Per-minute request allowance for your tier (keyed requests only)","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current minute window","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"total":{"type":"integer"},"page":{"type":"integer"},"limit":{"type":"integer"},"has_more":{"type":"boolean"},"lawyers":{"type":"array","items":{"$ref":"#/components/schemas/LawyerSummary"}}}}}}},"400":{"description":"Invalid request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded — honor Retry-After with jittered exponential backoff","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/lawyers/{lawyerId}":{"get":{"tags":["lawyers"],"operationId":"getLawyerProfile","summary":"Public lawyer profile","description":"Full public profile incl. bio and bar memberships. Bio is user-generated content wrapped in safety fencing for AI consumers. Scope: lawyers:read (anonymous allowed).","parameters":[{"name":"lawyerId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Profile","headers":{"X-Request-Id":{"description":"Unique id of this request — include it in support inquiries","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Per-minute request allowance for your tier (keyed requests only)","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current minute window","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/LawyerSummary"},{"type":"object","properties":{"bio":{"type":["string","null"]},"bar_memberships":{"type":"array","items":{"type":"object","properties":{"bar_name":{"type":"string"},"verified":{"type":"boolean"}}}}}}]}}}},"400":{"description":"Invalid request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found (or not verified)","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded — honor Retry-After with jittered exponential backoff","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/lawyers/{lawyerId}/availability":{"get":{"tags":["availability"],"operationId":"getAvailability","summary":"Available consultation slots","description":"Available slots for a date range (max 14 days). Scope: availability:read (anonymous allowed).","parameters":[{"name":"lawyerId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"start_date","in":"query","schema":{"type":"string","format":"date"},"description":"Default: today"},{"name":"end_date","in":"query","schema":{"type":"string","format":"date"},"description":"Default: start_date + 13 days"}],"responses":{"200":{"description":"Slots","headers":{"X-Request-Id":{"description":"Unique id of this request — include it in support inquiries","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Per-minute request allowance for your tier (keyed requests only)","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current minute window","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"lawyer_id":{"type":"string","format":"uuid"},"lawyer_name":{"type":"string"},"consultation_fee_cents":{"type":"integer"},"currency":{"type":"string"},"platform_commission_percent":{"type":"integer"},"slots":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/Slot"},{"type":"object","properties":{"available":{"type":"boolean"}}}]}},"booking_url":{"type":"string","format":"uri"},"profile_url":{"type":"string","format":"uri"}}}}}},"400":{"description":"Invalid request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded — honor Retry-After with jittered exponential backoff","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/lawyers/{lawyerId}/reviews":{"get":{"tags":["lawyers"],"operationId":"getLawyerReviews","summary":"Public reviews","description":"Recent reviews from verified clients. Comments are UGC with AI-safety fencing. Scope: reviews:read (anonymous allowed).","parameters":[{"name":"lawyerId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":25,"default":10}}],"responses":{"200":{"description":"Reviews","headers":{"X-Request-Id":{"description":"Unique id of this request — include it in support inquiries","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Per-minute request allowance for your tier (keyed requests only)","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current minute window","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"lawyer_id":{"type":"string","format":"uuid"},"lawyer_name":{"type":"string"},"average_rating":{"type":["number","null"]},"total_bookings":{"type":["integer","null"]},"reviews":{"type":"array","items":{"type":"object","properties":{"rating":{"type":"integer","minimum":1,"maximum":5},"comment":{"type":["string","null"]},"date":{"type":["string","null"],"format":"date-time"}}}}}}}}},"400":{"description":"Invalid request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded — honor Retry-After with jittered exponential backoff","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/practice-areas":{"get":{"tags":["reference"],"operationId":"getPracticeAreas","summary":"Practice-area hierarchy","description":"Reference data; cache-friendly (24h). Scope: reference:read (anonymous allowed).","responses":{"200":{"description":"Practice areas","content":{"application/json":{"schema":{"type":"object","properties":{"practice_areas":{"type":"array","items":{"type":"object"}}}}}}},"400":{"description":"Invalid request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded — honor Retry-After with jittered exponential backoff","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/cities":{"get":{"tags":["reference"],"operationId":"getCities","summary":"Supported cities and provinces","description":"Reference data; cache-friendly (24h). Scope: reference:read (anonymous allowed).","responses":{"200":{"description":"Cities grouped with provinces","content":{"application/json":{"schema":{"type":"object","properties":{"provinces":{"type":"array","items":{"type":"object"}},"cities":{"type":"array","items":{"type":"object"}}}}}}},"400":{"description":"Invalid request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded — honor Retry-After with jittered exponential backoff","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/bookings/initiate":{"post":{"tags":["bookings"],"operationId":"initiateBooking","summary":"Create a booking intent","description":"Creates a 15-minute booking intent for a free slot. The USER must open confirmation_url on leyapp.es to confirm and pay — the API never charges anyone. Supports Idempotency-Key (Stripe semantics: 24h replay, 422 on body mismatch). Test keys validate fully but create nothing. Scope: bookings:initiate (anonymous allowed at 5/hour).","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":255}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["lawyer_id","date","start_time","end_time"],"properties":{"lawyer_id":{"type":"string","format":"uuid"},"date":{"type":"string","format":"date"},"start_time":{"type":"string","example":"09:00:00"},"end_time":{"type":"string","example":"10:00:00"},"source":{"type":"string","enum":["ai_agent","api","widget"]},"agent_name":{"type":"string","maxLength":120}}}}}},"responses":{"201":{"description":"Intent created (or test-simulated)","headers":{"X-Request-Id":{"description":"Unique id of this request — include it in support inquiries","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Per-minute request allowance for your tier (keyed requests only)","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current minute window","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"booking_intent_id":{"type":["string","null"]},"test":{"type":"boolean","description":"Present and true for test keys"},"confirmation_url":{"type":"string","format":"uri"},"expires_at":{"type":"string","format":"date-time"},"slot":{"$ref":"#/components/schemas/Slot"},"lawyer":{"type":"object"},"message":{"type":"string"}}}}}},"400":{"description":"Invalid request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Slot unavailable (deliberately generic — no enumeration)","content":{"application/json":{"schema":{"type":"object"}}}},"422":{"description":"Idempotency-Key reused with a different body","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded — honor Retry-After with jittered exponential backoff","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/booking-intents/{intentId}":{"get":{"tags":["bookings"],"operationId":"getBookingIntentStatus","summary":"Poll a booking intent","description":"Status of an intent CREATED BY YOUR KEY: pending | confirmed | expired. Requires an API key with bookings:read (not available anonymously).","security":[{"apiKey":[]}],"parameters":[{"name":"intentId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Status","headers":{"X-Request-Id":{"description":"Unique id of this request — include it in support inquiries","schema":{"type":"string"}},"X-RateLimit-Limit":{"description":"Per-minute request allowance for your tier (keyed requests only)","schema":{"type":"integer"}},"X-RateLimit-Remaining":{"description":"Requests remaining in the current minute window","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"booking_intent_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["pending","confirmed","expired"]},"slot":{"$ref":"#/components/schemas/Slot"},"expires_at":{"type":"string","format":"date-time"},"confirmed_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"},"confirmation_url":{"type":"string","format":"uri"}}}}}},"400":{"description":"Invalid request","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"API key required","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found or not created by this key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit exceeded — honor Retry-After with jittered exponential backoff","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}}}}