openapi: 3.0.3
info:
  title: Ecosmart price API
  version: "1.0"
  description: |
    Live wholesale spot prices and forecasts for Home Assistant, a battery, or
    a script. Same figures the Ecosmart app shows.

    Base URL: `https://api.ecosmart.co.nz/rest/v1`

    These are wholesale energy prices at the grid exit point. They are not a
    complete retail rate. Forecasts are indicative, not a promise.

    Timestamps are ISO-8601 UTC (`Z`). `tradingDate` and `tradingPeriod` are
    New Zealand trading-calendar fields.

    Human docs: https://www.ecosmart.co.nz/electricity/api/
  contact:
    name: Ecosmart
    url: https://www.ecosmart.co.nz/contact/
servers:
  - url: https://api.ecosmart.co.nz/rest/v1
    description: Production
tags:
  - name: me
  - name: prices
  - name: icp
  - name: windows
security:
  - bearerAuth: []
paths:
  /me:
    get:
      tags: [me]
      operationId: getMe
      summary: What this key is, and which ICPs / GXPs it may use
      responses:
        "200":
          description: Key identity
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Identity"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"
  /gxps/{poc}/spot:
    get:
      tags: [prices]
      operationId: getSpot
      summary: Current 5-minute dispatch price
      parameters:
        - $ref: "#/components/parameters/poc"
      responses:
        "200":
          description: Current price. Stale or missing data is still 200 with isStale true.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Spot"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/UnknownPoc"
        "429":
          $ref: "#/components/responses/RateLimited"
  /gxps/{poc}/spot/history:
    get:
      tags: [prices]
      operationId: getSpotHistory
      summary: 5-minute dispatch history
      parameters:
        - $ref: "#/components/parameters/poc"
        - name: hours
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 72
            default: 24
          description: Clamped to 1–72 (the retention window). The response echoes the clamped value.
      responses:
        "200":
          description: History points
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SpotHistory"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/UnknownPoc"
        "429":
          $ref: "#/components/responses/RateLimited"
  /gxps/{poc}/forecast:
    get:
      tags: [prices]
      operationId: getForecast
      summary: Forward WITS prices (short schedule beats long)
      parameters:
        - $ref: "#/components/parameters/poc"
        - name: hours
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 168
            default: 48
          description: Requested horizon. Read coveredHours in the body for what you actually got.
      responses:
        "200":
          description: Forecast. Empty is a valid 200 with unavailableReason set.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Forecast"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/UnknownPoc"
        "429":
          $ref: "#/components/responses/RateLimited"
  /gxps/{poc}/final-prices:
    get:
      tags: [prices]
      operationId: getFinalPrices
      summary: Settled half-hourly prices
      parameters:
        - $ref: "#/components/parameters/poc"
        - name: from
          in: query
          required: true
          schema:
            type: string
            format: date
        - name: to
          in: query
          required: true
          schema:
            type: string
            format: date
            description: Inclusive. Maximum 62 days from `from`.
      responses:
        "200":
          description: Settled points. Missing periods are omitted, not null.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FinalPrices"
        "400":
          $ref: "#/components/responses/InvalidRange"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/UnknownPoc"
        "429":
          $ref: "#/components/responses/RateLimited"
  /icps/{icp}:
    get:
      tags: [icp]
      operationId: getIcp
      summary: One of this key's connection points
      parameters:
        - $ref: "#/components/parameters/icp"
      responses:
        "200":
          description: Recorded on the key at mint time — no registry lookup.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Icp"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/IcpNotInScope"
        "429":
          $ref: "#/components/responses/RateLimited"
  /windows:
    get:
      tags: [windows]
      operationId: getWindows
      summary: Tomorrow's cheapest and most expensive half-hours, plus network TOU
      parameters:
        - name: icp
          in: query
          required: true
          schema:
            type: string
        - name: n
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 24
            default: 6
          description: How many cheap and how many expensive half-hours. Clamped to 1–24.
      responses:
        "200":
          description: Ranked windows. Empty `cheap`/`dear` arrays is a valid 200 with unavailableReason set.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Windows"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/IcpNotInScope"
        "429":
          $ref: "#/components/responses/RateLimited"
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: ecos_live
      description: `Authorization: Bearer ecos_live_…`
  parameters:
    poc:
      name: poc
      in: path
      required: true
      schema:
        type: string
        example: HAY2201
      description: Grid exit point code.
    icp:
      name: icp
      in: path
      required: true
      schema:
        type: string
        example: "0000123456ABCDE"
      description: 15-character ICP identifier.
  responses:
    Unauthorized:
      description: Missing, unknown, or revoked key.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            code: UNAUTHORIZED
            message: Provide a valid key as Authorization Bearer ecos_live_…
    IcpNotInScope:
      description: This key does not carry that ICP.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            code: ICP_NOT_IN_SCOPE
            message: This key does not carry that ICP.
    UnknownPoc:
      description: No such grid exit point.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            code: UNKNOWN_POC
            message: No such grid exit point.
    InvalidRange:
      description: Date range is backwards or longer than 62 days.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            code: INVALID_RANGE
            message: range is 90 days; the maximum per call is 62.
    RateLimited:
      description: Per-key budget spent. Honour Retry-After.
      headers:
        Retry-After:
          schema:
            type: integer
          description: Whole seconds to wait.
        X-RateLimit-Limit:
          schema:
            type: integer
        X-RateLimit-Remaining:
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            code: RATE_LIMITED
            message: Slow down.
  schemas:
    Error:
      type: object
      required: [code, message]
      properties:
        code:
          type: string
        message:
          type: string
    IcpScope:
      type: object
      properties:
        icp:
          type: string
        poc:
          type: string
        network:
          type: string
        priceCategoryCode:
          type: string
    Identity:
      type: object
      properties:
        keyPrefix:
          type: string
        label:
          type: string
        accountRef:
          type: string
        allowedIcps:
          type: array
          items:
            $ref: "#/components/schemas/IcpScope"
        rateLimitPerMinute:
          type: integer
        createdAt:
          type: string
          format: date-time
        lastUsedAt:
          type: string
          format: date-time
          nullable: true
        gstRatePercent:
          type: number
        pricesAre:
          type: string
    Spot:
      type: object
      properties:
        poc:
          type: string
        priceDollarsPerMwh:
          type: number
          nullable: true
        priceCentsPerKwh:
          type: number
          nullable: true
        priceCentsPerKwhInclGst:
          type: number
          nullable: true
        observedAt:
          type: string
          format: date-time
          nullable: true
        ageSeconds:
          type: integer
          nullable: true
        isStale:
          type: boolean
        gstRatePercent:
          type: number
    SpotPoint:
      type: object
      properties:
        observedAt:
          type: string
          format: date-time
        priceDollarsPerMwh:
          type: number
        priceCentsPerKwh:
          type: number
        priceCentsPerKwhInclGst:
          type: number
    SpotHistory:
      type: object
      properties:
        poc:
          type: string
        hours:
          type: integer
        from:
          type: string
          format: date-time
        to:
          type: string
          format: date-time
        count:
          type: integer
        gstRatePercent:
          type: number
        points:
          type: array
          items:
            $ref: "#/components/schemas/SpotPoint"
    ForecastPoint:
      type: object
      properties:
        startsAt:
          type: string
          format: date-time
        tradingDate:
          type: string
          format: date
        tradingPeriod:
          type: integer
        schedule:
          type: string
          enum: [S, L]
        priceDollarsPerMwh:
          type: number
        priceCentsPerKwh:
          type: number
        priceCentsPerKwhInclGst:
          type: number
    Forecast:
      type: object
      properties:
        poc:
          type: string
        horizonHours:
          type: integer
        coveredHours:
          type: number
        from:
          type: string
          format: date-time
          nullable: true
        to:
          type: string
          format: date-time
          nullable: true
        publishedAt:
          type: string
          format: date-time
          nullable: true
        unavailableReason:
          type: string
          nullable: true
        count:
          type: integer
        gstRatePercent:
          type: number
        points:
          type: array
          items:
            $ref: "#/components/schemas/ForecastPoint"
    FinalPricePoint:
      type: object
      properties:
        tradingDate:
          type: string
          format: date
        tradingPeriod:
          type: integer
        startsAt:
          type: string
          format: date-time
        priceDollarsPerMwh:
          type: number
        priceCentsPerKwh:
          type: number
        priceCentsPerKwhInclGst:
          type: number
        priceType:
          type: string
          enum: [FINAL, INTERIM, DISPATCH]
        computed:
          type: boolean
    FinalPrices:
      type: object
      properties:
        poc:
          type: string
        from:
          type: string
          format: date
        to:
          type: string
          format: date
        dayCount:
          type: integer
        count:
          type: integer
        gstRatePercent:
          type: number
        source:
          type: string
        points:
          type: array
          items:
            $ref: "#/components/schemas/FinalPricePoint"
    Icp:
      type: object
      properties:
        icp:
          type: string
        poc:
          type: string
        network:
          type: string
        priceCategoryCode:
          type: string
        source:
          type: string
    Window:
      type: object
      properties:
        startsAt:
          type: string
          format: date-time
        endsAt:
          type: string
          format: date-time
        tradingPeriod:
          type: integer
        priceDollarsPerMwh:
          type: number
        priceCentsPerKwh:
          type: number
        priceCentsPerKwhInclGst:
          type: number
    NetworkTou:
      type: object
      properties:
        category:
          type: string
        network:
          type: string
        pricingYear:
          type: integer
        peak:
          type: array
          items:
            type: string
        offPeak:
          type: array
          items:
            type: string
        shoulder:
          type: array
          items:
            type: string
        night:
          type: array
          items:
            type: string
        exportCredit:
          type: array
          items:
            type: string
    Windows:
      type: object
      properties:
        icp:
          type: string
        poc:
          type: string
        tradingDate:
          type: string
          format: date
        indicative:
          type: boolean
        source:
          type: string
        advisory:
          type: string
        publishedAt:
          type: string
          format: date-time
          nullable: true
        unavailableReason:
          type: string
          nullable: true
        cheap:
          type: array
          items:
            $ref: "#/components/schemas/Window"
        dear:
          type: array
          items:
            $ref: "#/components/schemas/Window"
        networkTou:
          $ref: "#/components/schemas/NetworkTou"
