openapi: 3.1.0
info:
  title: Tango Space Building and Floor Outbound API
  version: "1.0"
  description: |
    Async **delta** REST API: client retrieves Building and Floor records
    that were updated in Tango since the last integration run. Single
    endpoint returns both entity types in one stream — `type` field
    disambiguates (`BUILDING` / `FLOOR`). Floor-specific fields are only
    populated when `type = FLOOR`.

    Updates to `building_id`, `floor_id`, and `lease_number` do NOT trigger
    export — only updates to other fields do. Null fields are omitted from
    the response.

    Direction NOTE: source header says `Source: CLIENT SYSTEM / Destination:
    TANGO` but the description is "outbound Building/Floor data from Tango
    to client". Treated as outbound (Tango → Client).
  x-direction: outbound
  x-source: Tango
  x-destination: Client System
servers:
  - url: https://api.tangoanalytics.com
    description: Production
  - url: https://testapi.tangoanalytics.com
    description: Test (SIT/UAT)
tags:
  - name: Buildings & Floors (Delta)
  - name: Status
paths:
  /v1/spacemgmt/delta/buildings:
    get:
      summary: Initiate delta Building/Floor export (async)
      operationId: getBuildingsAndFloorsDelta
      tags: [Buildings & Floors (Delta)]
      parameters:
        - $ref: "#/components/parameters/XClient"
        - $ref: "#/components/parameters/XApiKey"
      responses:
        "200":
          description: Async ack with gu_id.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AcceptedResponse"
  /v1/batch/status:
    get:
      summary: Retrieve paginated Building/Floor records by gu_id
      operationId: getBuildingsAndFloorsByGuId
      tags: [Status]
      parameters:
        - $ref: "#/components/parameters/XClient"
        - $ref: "#/components/parameters/XApiKey"
        - in: query
          name: gu_id
          required: true
          schema: { type: string, format: uuid }
        - in: query
          name: page_no
          required: false
          schema: { type: integer, minimum: 1 }
      responses:
        "200":
          description: Paginated records (5000/page default).
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RecordPage"
components:
  securitySchemes:
    xClientHeader: { type: apiKey, in: header, name: x-client }
    xApiKeyHeader: { type: apiKey, in: header, name: x-api-key }
  parameters:
    XClient:
      name: x-client
      in: header
      required: true
      schema: { type: string }
    XApiKey:
      name: x-api-key
      in: header
      required: true
      schema: { type: string }
  schemas:
    AcceptedResponse:
      type: object
      required: [status]
      properties:
        status: { type: string, enum: [OK, Error] }
        status_message: { type: string }
        gu_id: { type: string, format: uuid }
    RecordPage:
      type: object
      required: [status, records]
      properties:
        status: { type: string, enum: [PROCESSING, COMPLETED, ERROR] }
        total_records: { type: integer }
        total_pages: { type: integer }
        page_size: { type: integer, default: 5000 }
        page_no: { type: integer }
        records:
          type: array
          items:
            $ref: "#/components/schemas/BuildingOrFloorRecord"
    BuildingOrFloorRecord:
      type: object
      description: |
        One Building or Floor entry. `type` discriminates. Floor-specific
        fields (`floor_id`, `floor_number`, `floor_name`, floor-area fields)
        populate only when `type = FLOOR`. Null fields omitted in transit.
      additionalProperties: true
      properties:
        type:
          type: string
          enum: [BUILDING, FLOOR]
        building_id: { type: string }
        building_number: { type: string, description: 'Client Label "Store Number".' }
        building_name: { type: string, description: 'Client Label "Store Name".' }
        floor_id: { type: string }
        floor_number: { type: string, description: "Populated when type=FLOOR." }
        floor_name: { type: string, description: "Populated when type=FLOOR." }
        status: { type: string }
        address: { type: string }
        address2: { type: string }
        city: { type: string }
        state: { type: string }
        zip: { type: string }
        county: { type: string }
        country_code: { type: string }
        latitude: { type: number }
        longitude: { type: number }
        lease_number: { type: string }
        lease_exp_date: { type: string, description: "DATE." }
        operations_manager: { type: string }
        real_estate_manager: { type: string }
        facility_type: { type: string }
        ownership_type: { type: string }
        contractual_type: { type: string }
        measurement_type: { type: string }
        occupancy_status: { type: string }
        allocation_type: { type: string }
        charge_type: { type: string }
        delivery: { type: string, description: 'Y/N. Client Label "Delivery?".' }
        dining_area: { type: string, description: 'Y/N. Client Label "Dining Area?".' }
        is_subleased: { type: string }
        bld_finance_grp: { type: string, description: "Building Finance Group." }
        timezone: { type: string }
        territory: { type: string }
        building_desc: { type: string }
        building_rating: { type: string }
        loading_dock: { type: string }
        generator: { type: string }
        purchase_date: { type: string, description: "DATE." }
        # Area measurements (sqft + sqm pairs)
        total_area_sf: { type: number }
        total_area_sm: { type: number }
        external_gross_sf: { type: number }
        external_gross_sm: { type: number }
        internal_gross_sf: { type: number }
        internal_gross_sm: { type: number }
        cad_rentable_sf: { type: number, description: "Building Rentable sqft." }
        cad_rentable_sm: { type: number, description: "Building Rentable sqm." }
        lease_rentable_sf: { type: number }
        lease_rentable_sm: { type: number }
        lease_rentable_manual_sf: { type: number, description: "Lease RSF Manual Entry sqft." }
        lease_rentable_manual_sm: { type: number }
        usable_sf: { type: number, description: "Building Usable sqft." }
        usable_sm: { type: number }
        actual_sf: { type: number, description: "Building Assignable sqft." }
        actual_sm: { type: number }
        vertical_penetration_sf: { type: number }
        vertical_penetration_sm: { type: number }
        property_common_sf: { type: number }
        property_common_sm: { type: number }
        building_common_sf: { type: number }
        building_common_sm: { type: number }
        floor_common_sf: { type: number }
        floor_common_sm: { type: number }
        floor_shared_sf: { type: number }
        floor_shared_sm: { type: number }
        building_shared_sf: { type: number }
        building_shared_sm: { type: number }
        residual_sf: { type: number }
        residual_sm: { type: number }
        subleased_sf: { type: number }
        subleased_sm: { type: number }
        vacant_sf: { type: number }
        vacant_sm: { type: number }
        # Workstation metrics (dedicated / flex / shift / hotel)
        dedicated_wkst_area_sf: { type: number }
        dedicated_wkst_area_sm: { type: number }
        dedicated_wkst_capacity: { type: number }
        dedicated_wkst_occupancy: { type: number }
        dedicated_wkst_occ_rate: { type: number }
        dedicated_wkst_count: { type: number }
        dedicated_vacant_area_sf: { type: number }
        dedicated_vacant_area_sm: { type: number }
        flex_wkst_area_sf: { type: number }
        flex_wkst_area_sm: { type: number }
        flex_wkst_capacity: { type: number }
        flex_wkst_occupancy: { type: number }
        flex_wkst_occ_rate: { type: number }
        flex_wkst_count: { type: number }
        shift_wkst_area_sf: { type: number }
        shift_wkst_area_sm: { type: number }
        shift_wkst_capacity: { type: number }
        shift_wkst_occupancy: { type: number }
        shift_wkst_occ_rate: { type: number }
        shift_wkst_count: { type: number }
        hotel_wkst_area_sf: { type: number }
        hotel_wkst_area_sm: { type: number }
        hotel_wkst_capacity: { type: number }
        hotel_wkst_occupancy: { type: number }
        hotel_wkst_occ_rate: { type: number }
        hotel_wkst_count: { type: number }
        total_wkst_area_sf: { type: number }
        total_wkst_area_sm: { type: number }
        total_wkst_count: { type: number }
        # Density / capacity rollups
        area_per_wkst: { type: number }
        vacancy_pct: { type: number }
        wkst_density: { type: number }
        shared_vacant_area_sf: { type: number }
        shared_vacant_area_sm: { type: number }
        shift_vacant_area_sf: { type: number }
        shift_vacant_area_sm: { type: number }
        company_occupied_area_sf: { type: number }
        company_occupied_area_sm: { type: number }
        total_capacity: { type: number }
        total_headcount: { type: number }
        workstation_headcount: { type: number }
        workstation_capacity: { type: number }
        pct_building: { type: number }
        capacity_available: { type: number }
        capacity_planned: { type: number }
        # Parking
        parking_spots_total: { type: number }
        parking_spots_hc: { type: number, description: "Handicap." }
        parking_spots_ev: { type: number, description: "EV." }
        parking_spots_res: { type: number, description: "Reserved." }
        # Cost
        yearly_operating_cost: { type: number }
        total_ownership_cost: { type: number }
        tco_per_wkst: { type: number }
        tco_per_capacity: { type: number }
        # Extension attributes
        c_ext_attr1:  { type: string }
        c_ext_attr2:  { type: string }
        c_ext_attr3:  { type: string }
        c_ext_attr4:  { type: string }
        c_ext_attr5:  { type: string }
        c_ext_attr6:  { type: string }
        c_ext_attr7:  { type: string }
        c_ext_attr8:  { type: string }
        c_ext_attr9:  { type: string }
        c_ext_attr10: { type: string }
        n_ext_attr1:  { type: number, description: "NOTE: source typo `n_ext-attr1` (hyphen) preserved verbatim per data mapping; runtime likely uses underscore." }
        n_ext_attr2:  { type: number }
        n_ext_attr3:  { type: number }
        n_ext_attr4:  { type: number }
        n_ext_attr5:  { type: number }
        n_ext_attr6:  { type: number }
        n_ext_attr7:  { type: number }
        n_ext_attr8:  { type: number }
        n_ext_attr9:  { type: number }
        n_ext_attr10: { type: number }
        d_ext_attr1:  { type: string, description: "DATE." }
        d_ext_attr2:  { type: string, description: "DATE." }
        d_ext_attr3:  { type: string, description: "DATE." }
        d_ext_attr4:  { type: string, description: "DATE." }
        d_ext_attr5:  { type: string, description: "DATE." }
        d_ext_attr6:  { type: string, description: "DATE." }
        d_ext_attr7:  { type: string, description: "DATE." }
        d_ext_attr8:  { type: string, description: "DATE." }
        d_ext_attr9:  { type: string, description: "DATE." }
        d_ext_attr10: { type: string, description: "DATE." }
        c_ext_lov1: { type: string }
        c_ext_lov2: { type: string }
        c_ext_lov3: { type: string }
        c_ext_lov4: { type: string }
        c_ext_lov5: { type: string }
        c_ext_lov6: { type: string }
        c_ext_lov7: { type: string }
        c_ext_lov8: { type: string }
        c_ext_lov9: { type: string }
        c_ext_lov10: { type: string }
security:
  - xClientHeader: []
    xApiKeyHeader: []
