openapi: 3.1.0
info:
  title: AbuDhabiSchools Developer API
  version: 1.0.0
  summary: Structured Abu Dhabi private-school intelligence
  description: |
    Versioned, read-only school data for property, relocation and AI products.
    Test keys are limited to non-commercial evaluation. The commercial surface
    contains confirmed-permission ADEK facts and AbuDhabiSchools-derived metrics;
    community mappings and exam results are excluded.
  termsOfService: https://abudhabischools.com/developers/terms/
  contact:
    name: AbuDhabiSchools.com Developer Support
    email: hello@abudhabischools.com
    url: https://abudhabischools.com/developers/
servers:
  - url: https://abudhabischools.com/v1
    description: Production and evaluation endpoint
security:
  - ApiKeyAuth: []
tags:
  - name: Schools
  - name: Location
  - name: Rankings
  - name: Metadata
paths:
  /schools:
    get:
      operationId: listSchools
      tags: [Schools]
      summary: List and filter schools
      parameters:
        - $ref: '#/components/parameters/Query'
        - $ref: '#/components/parameters/Curriculum'
        - $ref: '#/components/parameters/Rating'
        - $ref: '#/components/parameters/FeeMin'
        - $ref: '#/components/parameters/FeeMax'
        - $ref: '#/components/parameters/Age'
        - $ref: '#/components/parameters/YearGroup'
        - $ref: '#/components/parameters/ScoreMin'
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
        - $ref: '#/components/parameters/Sort'
      responses:
        '200': { $ref: '#/components/responses/SchoolList' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorised' }
        '429': { $ref: '#/components/responses/RateLimited' }
  /schools/search:
    get:
      operationId: searchSchools
      tags: [Schools]
      summary: Search schools
      parameters:
        - $ref: '#/components/parameters/Query'
        - $ref: '#/components/parameters/Curriculum'
        - $ref: '#/components/parameters/Rating'
        - $ref: '#/components/parameters/FeeMax'
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
      responses:
        '200': { $ref: '#/components/responses/SchoolList' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorised' }
        '429': { $ref: '#/components/responses/RateLimited' }
  /schools/nearby:
    get:
      operationId: findNearbySchools
      tags: [Location]
      summary: Find schools near coordinates
      description: Distances are straight-line kilometres, not route or drive-time estimates.
      parameters:
        - $ref: '#/components/parameters/Latitude'
        - $ref: '#/components/parameters/Longitude'
        - $ref: '#/components/parameters/Radius'
        - $ref: '#/components/parameters/Curriculum'
        - $ref: '#/components/parameters/Rating'
        - $ref: '#/components/parameters/FeeMax'
        - $ref: '#/components/parameters/Age'
        - $ref: '#/components/parameters/YearGroup'
        - $ref: '#/components/parameters/PageSize'
      responses:
        '200': { $ref: '#/components/responses/SchoolList' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorised' }
        '429': { $ref: '#/components/responses/RateLimited' }
  /schools/compare:
    post:
      operationId: compareSchools
      tags: [Schools]
      summary: Compare two to five schools
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [school_ids]
              properties:
                school_ids:
                  type: array
                  minItems: 2
                  maxItems: 5
                  uniqueItems: true
                  items:
                    oneOf:
                      - type: string
                      - type: integer
      responses:
        '200':
          description: Comparable school profiles
          content:
            application/json:
              schema:
                type: object
                required: [data, meta, dataset]
                properties:
                  data: { type: array, items: { $ref: '#/components/schemas/School' } }
                  meta: { type: object }
                  dataset: { $ref: '#/components/schemas/DatasetMeta' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorised' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '422': { $ref: '#/components/responses/BadRequest' }
        '429': { $ref: '#/components/responses/RateLimited' }
  /schools/{identifier}:
    get:
      operationId: getSchool
      tags: [Schools]
      summary: Get one school profile
      parameters:
        - $ref: '#/components/parameters/Identifier'
      responses:
        '200':
          description: School profile
          content:
            application/json:
              schema:
                type: object
                required: [data, meta]
                properties:
                  data: { $ref: '#/components/schemas/School' }
                  meta: { $ref: '#/components/schemas/DatasetMeta' }
        '401': { $ref: '#/components/responses/Unauthorised' }
        '404': { $ref: '#/components/responses/NotFound' }
        '429': { $ref: '#/components/responses/RateLimited' }
  /schools/{identifier}/fees:
    get:
      operationId: getSchoolFees
      tags: [Schools]
      summary: Get a structured annual fee schedule
      parameters:
        - $ref: '#/components/parameters/Identifier'
      responses:
        '200':
          description: Fee schedule in AED
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/FeeSchedule' }
                  meta: { $ref: '#/components/schemas/DatasetMeta' }
        '401': { $ref: '#/components/responses/Unauthorised' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
  /schools/{identifier}/inspections:
    get:
      operationId: getSchoolInspections
      tags: [Schools]
      summary: Get Irtiqaa inspection history
      parameters:
        - $ref: '#/components/parameters/Identifier'
      responses:
        '200':
          description: Inspection history
          content:
            application/json:
              schema: { type: object }
        '401': { $ref: '#/components/responses/Unauthorised' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '404': { $ref: '#/components/responses/NotFound' }
  /location/schools:
    get:
      operationId: matchSchoolsToLocation
      tags: [Location]
      summary: Property and relocation-optimised school lookup
      parameters:
        - $ref: '#/components/parameters/Latitude'
        - $ref: '#/components/parameters/Longitude'
        - $ref: '#/components/parameters/Radius'
        - $ref: '#/components/parameters/Age'
        - $ref: '#/components/parameters/YearGroup'
        - $ref: '#/components/parameters/Curriculum'
        - $ref: '#/components/parameters/FeeMin'
        - $ref: '#/components/parameters/FeeMax'
        - $ref: '#/components/parameters/Rating'
        - $ref: '#/components/parameters/PageSize'
      responses:
        '200': { $ref: '#/components/responses/SchoolList' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorised' }
        '429': { $ref: '#/components/responses/RateLimited' }
  /rankings:
    get:
      operationId: rankSchools
      tags: [Rankings]
      summary: Rank filtered schools by AbuDhabiSchools Score
      parameters:
        - $ref: '#/components/parameters/Curriculum'
        - $ref: '#/components/parameters/Rating'
        - $ref: '#/components/parameters/FeeMax'
        - $ref: '#/components/parameters/Page'
        - $ref: '#/components/parameters/PageSize'
      responses:
        '200': { description: Ranked schools with methodology disclaimer }
        '401': { $ref: '#/components/responses/Unauthorised' }
        '403': { $ref: '#/components/responses/Forbidden' }
  /meta:
    get:
      operationId: getApiMetadata
      tags: [Metadata]
      summary: Get dataset, plan and field-profile metadata
      responses:
        '200': { description: API metadata }
        '401': { $ref: '#/components/responses/Unauthorised' }
  /data-freshness:
    get:
      operationId: getDataFreshness
      tags: [Metadata]
      summary: Get freshness and coverage by source family
      responses:
        '200': { description: Freshness and coverage record }
        '401': { $ref: '#/components/responses/Unauthorised' }
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: An ads_test_ or ads_live_ key. Never expose keys in browser or mobile clients.
  parameters:
    Identifier:
      name: identifier
      in: path
      required: true
      schema: { oneOf: [{ type: string }, { type: integer }] }
      description: AbuDhabiSchools id, numeric id, slug or exact school name.
    Query:
      name: query
      in: query
      schema: { type: string, maxLength: 160 }
    Curriculum:
      name: curriculum
      in: query
      schema: { type: string }
      description: One or more comma-separated curricula or aliases.
    Rating:
      name: rating
      in: query
      schema: { type: string }
      description: One or more comma-separated exact Irtiqaa ratings.
    FeeMin:
      name: fee_min
      in: query
      schema: { type: integer, minimum: 0, maximum: 1000000 }
    FeeMax:
      name: fee_max
      in: query
      schema: { type: integer, minimum: 0, maximum: 1000000 }
    Age:
      name: age
      in: query
      schema: { type: integer, minimum: 3, maximum: 19 }
    YearGroup:
      name: year_group
      in: query
      schema: { type: string, maxLength: 40 }
      example: Year 4
    ScoreMin:
      name: score_min
      in: query
      schema: { type: number, minimum: 0, maximum: 100 }
    Latitude:
      name: latitude
      in: query
      schema: { type: number, minimum: -90, maximum: 90 }
    Longitude:
      name: longitude
      in: query
      schema: { type: number, minimum: -180, maximum: 180 }
    Radius:
      name: radius_km
      in: query
      schema: { type: number, minimum: 0.1, maximum: 50, default: 5 }
    Page:
      name: page
      in: query
      schema: { type: integer, minimum: 1, maximum: 10000, default: 1 }
    PageSize:
      name: page_size
      in: query
      schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
    Sort:
      name: sort
      in: query
      schema: { type: string, enum: [score, rating, name, fee_asc, fee_desc, distance] }
  schemas:
    Source:
      type: object
      required: [authority, label, url, attribution_required]
      properties:
        authority: { type: string }
        label: { type: string }
        url: { type: string, format: uri }
        attribution_required: { type: boolean }
    School:
      type: object
      required: [id, slug, regulatory_ids, name, emirate, curriculum, grades, location, adek, annual_fee_aed, data_quality, links, sources]
      properties:
        id: { type: string, pattern: '^ads_[0-9]+$' }
        slug: { type: string }
        regulatory_ids:
          type: object
          properties:
            adek_school_id: { type: [string, integer, 'null'] }
        name: { type: string }
        name_ar: { type: [string, 'null'] }
        emirate: { type: string, const: Abu Dhabi }
        curriculum: { type: object }
        grades: { type: object }
        location: { type: object }
        adek: { type: object }
        annual_fee_aed: { type: object }
        data_quality: { type: object }
        links: { type: object }
        sources: { type: array, items: { $ref: '#/components/schemas/Source' } }
        enrolment: { type: object }
        abudhabischools: { type: object }
        capacity: { type: object }
        distance_km: { type: number }
        matched_annual_fee_aed: { type: integer }
    FeeSchedule:
      type: object
      required: [school, currency, schedule, summary, source]
      properties:
        school: { type: object }
        currency: { type: string, const: AED }
        schedule:
          type: array
          items:
            type: object
            required: [stage, annual_tuition_aed]
            properties:
              stage: { type: string }
              annual_tuition_aed: { type: integer }
        summary: { type: object }
        history: { type: [object, 'null'] }
        source: { $ref: '#/components/schemas/Source' }
    Pagination:
      type: object
      required: [page, page_size, total, total_pages, has_next]
      properties:
        page: { type: integer }
        page_size: { type: integer }
        total: { type: integer }
        total_pages: { type: integer }
        has_next: { type: boolean }
    DatasetMeta:
      type: object
      required: [api_version, dataset_version, coverage, licence, data_policy, excluded_field_families, attribution]
      properties:
        api_version: { type: string }
        dataset_version: { type: string }
        coverage: { type: object }
        licence: { type: string, enum: [evaluation_non_commercial, licensed_commercial_api] }
        data_policy: { type: string, const: confirmed_permission_and_abudhabischools_derived_only }
        excluded_field_families:
          type: array
          items: { type: string, enum: [community_mapping, exam_results] }
        attribution: { type: string }
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message, request_id]
          properties:
            code: { type: string }
            message: { type: string }
            request_id: { type: string }
            docs_url: { type: string, format: uri }
            details: {}
  responses:
    SchoolList:
      description: Paginated school records
      headers:
        X-RateLimit-Limit: { schema: { type: integer } }
        X-RateLimit-Remaining: { schema: { type: integer } }
        X-Quota-Limit: { schema: { type: integer } }
        X-Quota-Remaining: { schema: { type: integer } }
        X-Request-Id: { schema: { type: string } }
      content:
        application/json:
          schema:
            type: object
            required: [data, pagination, meta]
            properties:
              data: { type: array, items: { $ref: '#/components/schemas/School' } }
              pagination: { $ref: '#/components/schemas/Pagination' }
              query: { type: object }
              meta: { $ref: '#/components/schemas/DatasetMeta' }
    BadRequest:
      description: Invalid request
      content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
    Unauthorised:
      description: Missing or invalid API key
      content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
    Forbidden:
      description: Endpoint or field family not included
      content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
    NotFound:
      description: Record not found
      content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
    RateLimited:
      description: Per-minute rate or monthly quota exceeded
      headers:
        Retry-After: { schema: { type: integer } }
      content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
