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: contact@legal.ge license: name: API access — no licence required for read-only use url: https://legal.ge/terms termsOfService: https://legal.ge/terms x-logo: url: https://legal.ge/apple-touch-icon.png altText: legal.ge backgroundColor: '#1B2233' 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 tags: - name: Matching description: Map a free-text legal question to practice areas, and optionally to ranked verified specialists. - name: Directory description: Look up services, practice areas and verified specialists on legal.ge. paths: /api/ask: post: operationId: askMatchSpecialists tags: - Matching 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 tags: - Matching 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 tags: - Matching 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 tags: - Directory 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 tags: - Directory 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 tags: - Directory 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.