{"openapi":"3.1.0","info":{"title":"legal.ge Public API","version":"1.0.0","summary":"Find verified legal specialists in Georgia (the country) from natural-language queries.","description":"Read-only API for finding verified Georgian legal specialists. Designed for AI agents (Perplexity, ChatGPT, Claude, Custom GPTs, MCP servers) and third-party integrations. Supports Georgian, English, and Russian. Sending an inquiry requires sign-in on legal.ge and is not exposed here.","contact":{"name":"legal.ge","url":"https://legal.ge","email":"info@legal.ge"},"license":{"name":"API access — no licence required for read-only use","url":"https://legal.ge/terms"},"termsOfService":"https://legal.ge/terms","x-mcp-server":{"npm":"@legalge/mcp","repository":"https://github.com/infolegalge/legal.ge-mcp","registry":"https://www.npmjs.com/package/@legalge/mcp"},"x-llms-txt":"https://legal.ge/llms.txt"},"servers":[{"url":"https://legal.ge"}],"paths":{"/api/ask":{"post":{"operationId":"askMatchSpecialists","summary":"Classify a free-text legal question and return matching verified specialists.","description":"Pass a natural-language description of the user's legal need (Georgian, English, or Russian). The endpoint maps the text to legal practice areas and returns ranked specialists who work in those areas, including a profile URL for each.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AskRequest"},"examples":{"english":{"value":{"query":"I need LLC registration in Tbilisi","locale":"en","limit":10}},"georgian":{"value":{"query":"მინდა შპს-ის რეგისტრაცია თბილისში","locale":"ka"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AskResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"}}},"get":{"operationId":"askMatchSpecialistsGet","summary":"Same as POST /api/ask but with query parameters.","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","maxLength":500}},{"name":"locale","in":"query","schema":{"type":"string","enum":["ka","en","ru"],"default":"ka"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":50,"default":20}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AskResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/ask/classify":{"get":{"operationId":"classifyLegalIntent","summary":"Classify free text into legal practice areas only.","description":"Lighter than /api/ask — returns just the matched categories with weighted scores and the keywords that fired. Useful when an agent wants to pick categories first, then query specialists separately.","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","maxLength":500}},{"name":"locale","in":"query","schema":{"type":"string","enum":["ka","en","ru"],"default":"ka"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClassifyResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/service-search":{"get":{"operationId":"searchServicesAndCategories","summary":"Search the published service + category taxonomy.","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":2}},{"name":"locale","in":"query","schema":{"type":"string","enum":["ka","en","ru"],"default":"ka"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ServiceSearchItem"}}}}}}}},"/api/specialists/search":{"get":{"operationId":"searchSpecialistsByName","summary":"Look up specialists by name fragment.","description":"Returns up to 20 specialists matching the query. Contact info is not included — use /api/specialists/{id}/contact to reveal phone or email for a specific specialist. Rate-limited at 30 requests/minute per IP.","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":3}},{"name":"locale","in":"query","schema":{"type":"string","enum":["ka","en","ru"],"default":"ka"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SpecialistNameResult"}}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/api/specialists/{id}/contact":{"get":{"operationId":"revealSpecialistContact","summary":"Reveal one contact field (phone or email) for a specialist or company.","description":"Click-to-reveal contact endpoint. Returns the requested field only if the profile has opted in (info_activate=true) and is in a public compliance state. Accepts SPECIALIST, SOLO_SPECIALIST, and COMPANY roles — despite the path name, companies share this endpoint since their contact info lives on the same profiles row. Rate-limited at 10 requests/minute per IP to make bulk PII harvest impractical.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"field","in":"query","required":true,"schema":{"type":"string","enum":["phone","email"]}}],"responses":{"200":{"description":"OK — returns only the requested field.","content":{"application/json":{"schema":{"oneOf":[{"type":"object","properties":{"phone":{"type":"string"}},"required":["phone"]},{"type":"object","properties":{"email":{"type":"string"}},"required":["email"]}]}}}},"400":{"description":"Invalid id or field parameter."},"404":{"description":"Specialist not found, not opted in, or no value for the requested field."},"429":{"$ref":"#/components/responses/RateLimited"}}}}},"components":{"responses":{"BadRequest":{"description":"Invalid request. The `error.code` field is machine-readable; possible values: `QUERY_REQUIRED`, `QUERY_TOO_LONG`, `INVALID_BODY`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"},"examples":{"queryRequired":{"value":{"error":{"code":"QUERY_REQUIRED","message":"query is required","field":"query","constraint":{"required":true}}}},"queryTooLong":{"value":{"error":{"code":"QUERY_TOO_LONG","message":"query exceeds 500 characters","field":"query","constraint":{"max_length":500},"received_length":712}}}}}}},"RateLimited":{"description":"Too many requests. The `Retry-After` header and `error.constraint.retry_after_seconds` both indicate when to retry.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds until the next request is allowed"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorEnvelope"},"examples":{"rateLimited":{"value":{"error":{"code":"RATE_LIMITED","message":"Too many requests","constraint":{"retry_after_seconds":42}}}}}}}}},"schemas":{"ErrorEnvelope":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Machine-readable error code. Stable across versions. Known values: QUERY_REQUIRED, QUERY_TOO_LONG, INVALID_BODY, RATE_LIMITED."},"message":{"type":"string","description":"Human-readable explanation suitable for surfacing to end users."},"field":{"type":"string","description":"Name of the offending request field, when the error is field-scoped."},"constraint":{"type":"object","additionalProperties":true,"description":"Machine-readable constraint that failed. Shape depends on `code`: `{required: true}`, `{max_length: 500}`, `{retry_after_seconds: 42}`, etc."},"received_length":{"type":"integer","description":"For QUERY_TOO_LONG: the actual length of the rejected query, so the caller can truncate and retry."}}}}},"AskRequest":{"type":"object","required":["query"],"properties":{"query":{"type":"string","maxLength":500,"description":"Natural-language legal question or situation."},"locale":{"type":"string","enum":["ka","en","ru"],"default":"ka"},"limit":{"type":"integer","minimum":1,"maximum":50,"default":20}}},"MatchedCategory":{"type":"object","description":"A node matched by the classifier. Can be a category (any depth) or an individual service.","properties":{"kind":{"type":"string","enum":["category","service"],"description":"`category` = a legal practice area at some depth in the taxonomy. `service` = a specific service under a category. Services are the deepest level the matcher considers."},"id":{"type":"string","format":"uuid"},"level":{"type":"integer","description":"Depth in the taxonomy: 1 = top-level practice area, 2/3 = sub-areas, 4+ = services (one level deeper than their parent category). The matcher narrows ancestors when a descendant matches."},"slug":{"type":"string"},"name":{"type":"string"},"match_confidence":{"type":"string","enum":["curated","name_fallback"],"description":"`curated` = the node was matched via one or more admin-curated keywords (strong signal). `name_fallback` = the matcher only found a token overlap with the node name itself (weaker, used when no curated keyword fired)."},"matched_keywords":{"type":"array","items":{"type":"string"},"description":"Keywords from the user query that triggered this match."},"parent_chain":{"type":"array","description":"Ancestors of this node, in child-first order (closest parent first, root last). Lets a caller cite the broader practice area without an extra request.","items":{"type":"object","properties":{"kind":{"type":"string","enum":["category","service"]},"id":{"type":"string","format":"uuid"},"level":{"type":"integer"},"slug":{"type":"string"},"name":{"type":"string"}}}}}},"AskSpecialist":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"slug":{"type":"string"},"full_name":{"type":"string"},"role_title":{"type":"string","nullable":true},"bio_excerpt":{"type":"string","nullable":true},"avatar_url":{"type":"string","nullable":true},"profile_url":{"type":"string","format":"uri"},"company":{"nullable":true,"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","nullable":true},"slug":{"type":"string","nullable":true}}},"professional_org":{"type":"string","nullable":true},"professional_orgs":{"type":"array","items":{"type":"object","properties":{"org_id":{"type":"string"},"registration_number":{"type":"string"}}}},"matched_categories":{"type":"array","items":{"type":"string"},"description":"Slugs of the categories this specialist matched against the query, in arbitrary order."},"contact":{"type":"object","properties":{"email":{"type":"string","nullable":true},"phone":{"type":"string","nullable":true}},"description":"Populated only when the specialist has opted into public contact display. Otherwise both fields are null and the user must sign in to message via legal.ge."}}},"AskResponse":{"type":"object","properties":{"query":{"type":"string"},"locale":{"type":"string","enum":["ka","en","ru"]},"matched_categories":{"type":"array","items":{"$ref":"#/components/schemas/MatchedCategory"}},"specialists":{"type":"array","items":{"$ref":"#/components/schemas/AskSpecialist"},"description":"Specialists ordered by legal.ge internal relevance ranking. The platform combines category match strength with internal quality and verification signals; exact ranking inputs are intentionally not exposed."},"ranking_note":{"type":"string","description":"Human-readable explanation of the ordering convention, suitable for surfacing to end users."},"meta":{"type":"object","properties":{"total":{"type":"integer"},"returned":{"type":"integer"},"fallback_used":{"type":"boolean"},"took_ms":{"type":"integer"}}}}},"ClassifyResponse":{"type":"object","properties":{"query":{"type":"string"},"locale":{"type":"string","enum":["ka","en","ru"]},"matched_categories":{"type":"array","items":{"$ref":"#/components/schemas/MatchedCategory"}},"meta":{"type":"object","properties":{"fallback_used":{"type":"boolean"}}}}},"ServiceSearchItem":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"type":{"type":"string","enum":["category","service"]},"displayName":{"type":"string"},"href":{"type":"string"},"categoryName":{"type":"string","nullable":true}}},"SpecialistNameResult":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"full_name":{"type":"string"},"avatar_url":{"type":"string","nullable":true},"slug":{"type":"string","nullable":true},"info_activate":{"type":"boolean","description":"True if the specialist has opted in to public contact CTAs. Use /api/specialists/{id}/contact to retrieve phone or email."}}}}}}