openapi: "3.0.3"
info:
  title: "OPUS"
  version: "1.0"
servers:
  - url: http://localhost:8080
    description: Local
components:
  parameters:
    api_key_id_param:
      name: api_key_id
      in: path
      schema:
        type: string
        format: uuid
      required: true
      description: "public id of the api_key"
  schemas:
    ApiKeyGenerateRequest:
      type: "object"
      description: "Generate an api key"
      properties:
        description:
          type: "string"
          description: "Description associated with the key, for example: 'Access key for automated system X version Y'"
      required:
        - description
    ApiKeyGenerateResponse:
      type: "object"
      properties:
        id:
          type: "string"
          format: uuid
          description: "Id of the key, can be used to delete the key later. The 'prefix' part of the key"
        api_key:
          type: "string"
          description: "Api key used for authenticating with opus using X-API-KEY header. Includes a 'prefix' part which can be used to identify the key."
      required:
        - id
        - api_key
    ApiKeyResponse:
      type: "object"
      properties:
        id:
          type: "string"
          format: uuid
          description: "Id of the key"
        description:
          type: "string"
          description: "Description associated with the key"
      required:
        - id
        - description
    NodeAggregateResponse:
      type: array
      description: Array of account aggregated validator statistics, each representing one day
      items:
        type: object
        description: Aggregated node statistics for account in a single day
        properties:
          when:
            type: string
            format: date
            example: "2019-05-17"
            description: Date for which aggregated statistics are shown
          total:
            type: integer
            description: Total number of node statistics
          active:
            type: integer
            description: Total number of active/validating nodes
          created:
            type: integer
            description: Number of created validators that haven't been deposited yet
          deposited:
            type: integer
            description: Total number of nodes for which deposit happened, but they are not yet on chain
          pending:
            type: integer
            description: Number of validators in pending state
          exited:
            type: integer
            description: Number of validators in exited state
          unstakeable:
            type: integer
            description: Number of validators that can be unstaked
          balance_active:
            type: string
            pattern: "^\\d+$"
            description: Sum of the balance for active nodes
          balance_total:
            type: string
            pattern: "^\\d+$"
            description: Sum of the balance for all nodes (except exited)
          requests:
            description: "Used to display about staking request activity and progress of validator status"
            type: array
            items:
              type: object
              properties:
                transaction_id:
                  type: string
                  pattern: "^0x[a-f,0-9]+$"
                  description: Eth1 chain deposit transaction ID. This field is empty for unstake requests
                request_id:
                  type: string
                  format: uuid
                  example: 9b4ec01f-c4e8-47c0-a2e0-fffc940dcc31
                  description: Opus internal request ID
                created_at:
                  type: string
                  description: "Timestamp when stake request was created, in milliseconds"
                request_type:
                  type: string
                  enum: [stake, unstake]
                  description: Action done on-chain in the work of this request
                network:
                  type: string
                validators:
                  type: array
                  items:
                    type: object
                    properties:
                      public_signing_key:
                        type: string
                      status:
                        type: string
                        enum:
                          [
                            "created",
                            "deposited",
                            "pending",
                            "active",
                            "exited",
                            "withdrawn",
                          ]
                    required:
                      - public_signing_key
                      - status
              required:
                - request_id
                - created_at
                - request_type
                - network
                - validators
          deposits:
            type: array
            items:
              type: object
              properties:
                transaction_id:
                  type: string
                  pattern: "^0x[a-f,0-9]+$"
                  description: Eth1 chain deposit transaction ID
                when:
                  type: string
                  format: date
                  example: "2019-05-17"
                  description: Date on which deposit was made
                block:
                  type: integer
                  description: Eth1 chain block where deposit happened
                progress:
                  type: object
                  properties:
                    pending:
                      type: integer
                      description: Number of validators in pending state
                    deposited:
                      type: integer
                      description: Total number of nodes for which deposit happened, but they are not yet on chain
                    active:
                      type: integer
                      description: Total number of active/validating nodes
                validators:
                  type: array
                  items:
                    type: object
                    properties:
                      public_signing_key:
                        type: string
                      status:
                        type: string
                        enum: ["deposited", "pending", "active"]
                    required:
                      - public_signing_key
                      - status
              required:
                - transaction_id
                - when
                - block
                - validators
        required:
          - when
          - total
          - active
          - created
          - deposited
          - pending
          - exited
          - unstakeable
          - balance_active
          - balance_total
          - deposits
          - requests

    RewardsAndEffectivenessResponse:
      type: array
      description: Array of daily rewards objects
      items:
        type: object
        description: |
          Rewards single day statistics. Since manipulating gwei as-is can break Javascript big numbers,
          the ethereum numbers are exposed as string values. API consumers must convert them to integers
          manually.
        properties:
          network:
            type: string
            enum: ["ethereum"]
            description: "Network for which to perform statistics request"
          when:
            type: string
            format: date
            example: "2019-05-17"
            description: |
              Start date in the aggregated time range for which rewards are shown.
              This date is always UTC.
          aggregate_days:
            type: integer
            format: date
            example: "2019-05-17"
            description: Number of days in a single aggregation
          delta:
            type: string
            pattern: "^\\d+$"
            description: Total earnings (in gwei). This can be negative values
          apr:
            type: number
            format: float
            description: total rewards divided by active stake over 365 days
          rewards:
            type: object
            description: Rewards (in gwei) for particular date. These values are always positive
            properties:
              total:
                type: string
                pattern: "^\\d+$"
                description: Total rewards as collected during the day (without penalties)
              execution_priority:
                type: string
                pattern: "^\\d+$"
                description: Priority fees for the date (including MEV)
              execution_mev:
                type: string
                pattern: "^\\d+$"
                description: MEV earnings
              consensus_block:
                type: string
                pattern: "^\\d+$"
                description: Consensus rewards for producing a block
              consensus_attestation:
                type: string
                pattern: "^\\d+$"
                description: Consensus rewards for attesting blocks and participating in sync committee
          penalties:
            type: object
            description: Penalties (in gwei) for particular date. These values are always positive.
            properties:
              total:
                type: string
                pattern: "^\\d+$"
                description: Total penalties in the day
              consensus_sync_committee:
                type: string
                pattern: "^\\d+$"
                description: Sync committee penalties
              consensus_attestation_missed:
                type: string
                pattern: "^\\d+$"
                description: Missed attestation penalties
              consensus_vote_late_target:
                type: string
                pattern: "^\\d+$"
                description: Late target vote penalties
              consensus_vote_wrong_target:
                type: string
                pattern: "^\\d+$"
                description: Wrong target vote penalties
              consensus_vote_late_source:
                type: string
                pattern: "^\\d+$"
                description: Late source vote penalties
          effectiveness:
            type: object
            description: Validators effectiveness (in %) aggregated over a sepcified time window. These values are always positive.
            properties:
              attester_effectiveness:
                type: number
                format: float
                description: The attester effectiveness over the requested time period
              proposer_effectiveness:
                type: number
                format: float
                description: The proposer effectiveness over the requested time period
              slashes:
                type: integer
                description: Number of validators slashed over the requested time period
    StatsRequest:
      type: "object"
      properties:
        from:
          type: string
          format: date
          example: "2019-05-17"
          description: |
            A starting day for which need to retrieve rewards.
            If not sent, the date of registration is used.
            This date should be always in UTC time zone.
        to:
          type: string
          format: date
          example: "2019-05-17"
          description: |
            An ending day for which need to retrieve rewards.
            By default, assumes it is current day.
            This date should be always in UTC time zone.
        aggregate_days:
          type: integer
          description: |
            Number of daily statistics aggregated in a single entry.
            The aggregation works as follows:
              - for percentage values, shows avg()
              - for gwei or count values, shows sum()
            By default, shows single day aggregations.
        wallets:
          type: array
          items:
            type: string
            pattern: "^(0x[a-fA-F0-9]{40})$"
            description: |
              Optionally submit an address of a wallet,
              so results will be filtered only to statistics related
              to that wallet.
        network:
          type: string
          enum: ["ethereum"]
          description: "Network for which to perform statistics request"
      required:
        - network
    DateParameter:
      type: string
      format: date
      example: "2019-05-17"
      description: |
        A starting day for which need to retrieve rewards.
        This date should be always in UTC time zone.
    ValidatorIndex:
      type: integer
      description: |
        Validator index on chain
    EffectivenessRating:
      type: number
      description: |
        Effectiveness rating as reported
    ValidatorBalance:
      type: string
      description: |
        Balance of validator
    ValidatorPublicKey:
      type: string
      description: |
        Validator public key, either with or without 0x prefix.
    ValidatorStatus:
      type: "string"
      description: "An optional status filter. Multiple statuses can be requested."
      enum: ["created", "deposited", "pending", "active", "exited", "withdrawn"]
    ValidatorsRequest:
      type: "object"
      properties:
        from:
          description: |
            A query range start parameter. Can be one of: date string
            in format "YYYY-MM-DD", integer validator on-chain index,
            float effectiveness rating, string that represents integer
            value of the balance in gwei, or validator public key (accepts both
            0x prefixed hex strings, and non-prefixed hex).
            If passed, must be of the same type as "to" parameter.
          oneOf:
            - $ref: "#/components/schemas/DateParameter"
            - $ref: "#/components/schemas/ValidatorIndex"
            - $ref: "#/components/schemas/EffectivenessRating"
            - $ref: "#/components/schemas/ValidatorBalance"
            - $ref: "#/components/schemas/ValidatorPublicKey"
        to:
          description: |
            A query range start parameter. Can be one of: date string
            in format "YYYY-MM-DD", integer validator on-chain index,
            float effectiveness rating, string that represents integer
            value of the balance in gwei, or validator public key (accepts both
            0x prefixed hex strings, and non-prefixed hex).
            If passed, must be of the same type as "from" parameter.
          oneOf:
            - $ref: "#/components/schemas/DateParameter"
            - $ref: "#/components/schemas/ValidatorIndex"
            - $ref: "#/components/schemas/EffectivenessRating"
            - $ref: "#/components/schemas/ValidatorBalance"
            - $ref: "#/components/schemas/ValidatorPublicKey"
        tie_break_pubkey:
          description: |
            When returning data on the validators response on
            the `continue_query`, some data might have the same value when ordering,
            for example, if we get validators using `ValidatorBalance`, the balance might
            be the same for multiple validators, to know where we continue the query from
            we use the public key as a tie-breaker.
          type: string
        status:
          type: array
          items:
            $ref: "#/components/schemas/ValidatorStatus"
        sort_by:
          type: string
          enum: ["index", "effectiveness", "balance", "status", "public_key"]
          description: |
            Sorting column to use (default is index)
        sort_order:
          type: string
          enum: ["asc", "desc"]
          description: |
            Sorting order (ascending or descending)
        validators:
          type: array
          items:
            type: "string"
            description: |
              Validator signing key, or uuid or on-chain index
          description: |
            Array of indices or public signing key,
            if empty returns all validators
        aggregate_days:
          type: integer
          description: |
            Number of daily statistics aggregated in a single entry.
            The aggregation works as follows:
              - for percentage values, shows avg()
              - for gwei or count values, shows sum()
            By default, shows single day aggregations.
        wallets:
          type: array
          items:
            type: string
            pattern: "^(0x[a-fA-F0-9]{40})$"
            description: |
              Optionally submit an address of a wallet,
              so results will be filtered only to statistics related
              to that wallet.
        network:
          type: string
          enum: ["ethereum"]
          description: "Network for which to perform statistics request"
      required:
        - network
        - from
        - to
    ValidatorsResponse:
      type: object
      properties:
        continue_query:
          $ref: "#/components/schemas/ValidatorsRequest"
        validators:
          type: "array"
          items:
            type: object
            properties:
              network:
                type: string
                enum: ["ethereum"]
                description: "Network for which to perform statistics request"
              balance:
                type: string
                pattern: "^\\d+$"
                description: Validator balance (in gwei)
              status:
                $ref: '#/components/schemas/ValidatorStatus'
              slashed:
                type: boolean
              mev_enabled:
                type: boolean
              index:
                type: integer
                description: On-chain index of validator (if missing, validator in deposited state)
              public_signing_key:
                type: string
              withdrawal_credentials:
                type: string
              rewards:
                $ref: "#/components/schemas/RewardsAndEffectivenessResponse"
              fee_recipient:
                type: string
            required:
              - status
              - balance
              - mev_enabled
              - rewards
              - withdrawal_credentials
              - public_signing_key
              - fee_recipient
      required:
        - validators
    StakingRequestsResponse:
      type: object
      title: "StakingRequestsResponse"
      properties:
        requests:
          title: "StakingRequests"
          description: "Used to display about staking request activity and progress of validator status"
          type: array
          items:
            type: object
            properties:
              request_id:
                type: string
                format: uuid
                example: 9b4ec01f-c4e8-47c0-a2e0-fffc940dcc31
                description: Opus internal request ID
              transaction_id:
                type: string
                pattern: "^0x[a-f,0-9]+$"
                description: Eth1 chain deposit transaction ID. This field is empty for unstake requests
              created_at:
                type: string
                description: "Timestamp when stake request was created, in milliseconds"
              network:
                type: string
                description: "Network for which staking request was performed"
              status:
                type: string
                description: "Status of the staking request"
              validators:
                title: "StakingRequestsValidators"
                type: array
                items:
                  type: object
                  properties:
                    status:
                      $ref: '#/components/schemas/ValidatorStatus'
                    index:
                      type: integer
                      format: int64
                      description: On-chain index of validator (if missing, validator in deposited state)
                    public_signing_key:
                      type: string
                    withdrawal_address:
                      type: string
                    amount:
                      type: integer
                    fee_recipient:
                      type: string
                    activated_at: 
                      type: string
                    activated_epoch: 
                      type: string
                    deposited_at: 
                      type: string
                    deposited_block:
                      type: string
                  required:
                    - network
                    - status
                    - public_signing_key
                    - withdrawal_address
                    - fee_recipient
            required:
              - request_id
              - created_at
              - request_type
              - network
              - status
              - validators
paths:
  /health:
    get:
      description: "healthcheck"
      responses:
        200:
          description: OK
  /version:
    get:
      description: "Version of code base"
      responses:
        200:
          description: OK
          content:
            "application/json":
              schema:
                title: VersionResponse
                type: "object"
                properties:
                  code_version:
                    type: "string"
                    description: "Version of code base"
                  url_versions:
                    type: "string"
                    description: "Supported URL versions"
                required:
                  - code_version
                  - url_versions
  /api/v1/auth:
    get:
      description: "verify authentication status"
      tags:
        - auth
      responses:
        200:
          description: OK
        403:
          description: Forbidden
        401:
          description: Unauthorized
  /api/v1/api_keys:
    get:
      tags:
        - auth
      description: "list api keys"
      responses:
        200:
          description: OK
          content:
            "application/json":
              schema:
                title: ListApiKeysResponse
                type: "array"
                items:
                  $ref: "#/components/schemas/ApiKeyResponse"
        403:
          description: Forbidden
  /api/v1/api_keys/generate:
    post:
      tags:
        - auth
      description: "Generate a new api_key for use with X-API-KEY auth method. The returned key needs to be saved by the caller, as it isn't stored in the system."
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              $ref: "#/components/schemas/ApiKeyGenerateRequest"
      responses:
        200:
          description: Ok
          content:
            "application/json":
              schema:
                $ref: "#/components/schemas/ApiKeyGenerateResponse"
        403:
          description: Forbidden
  /api/v1/api_keys/{api_key_id}:
    parameters:
      - $ref: "#/components/parameters/api_key_id_param"
    get:
      tags:
        - auth
      description: "inspect the api_key entry"
      responses:
        200:
          description: OK
          content:
            "application/json":
              schema:
                $ref: "#/components/schemas/ApiKeyResponse"
        403:
          description: Forbidden
        404:
          description: Not found
    delete:
      tags:
        - auth
      description: "Delete given API KEY."
      responses:
        200:
          description: OK
        400: 
          description: "Bad Request - User needs to hold at least one API-KEY, otherwise endpoint will return 400."
        403:
          description: Forbidden
        404:
          description: Not found
  /api/v1/stake:
    post:
      summary: Stake x amounts of 32ETH on Ethereum
      tags:
        - staking
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              title: "StakeRequest"
              type: "object"
              properties:
                request_id:
                  type: "string"
                  format: "uuid"
                  description: "Stake request id used to track request when polling the endpoint."
                fee_recipient:
                  type: string
                  pattern: "^(0x[a-fA-F0-9]{40})$"
                  description: "Ethereum address for receiving execution fees"
                withdrawal_address:
                  type: string
                  pattern: "^(0x[a-fA-F0-9]{40})$"
                  description: "Ethereum execution address for receiving deposit and consensus fees"
                deposit_address:
                  type: string
                  pattern: "^(0x[a-fA-F0-9]{40})$"
                  description: "Ethereum execution address which will be used for making the validator deposit."
                amount:
                  type: integer
                  format: integer
                  description: "Amount of ETH, should be in multiples of 32ETH"
                network:
                  type: string
                  enum: ["ethereum"]
                  description: "Network for which to perform staking request"
              required:
                - request_id
                - fee_recipient
                - withdrawal_address
                - deposit_address
                - amount
                - network
      responses:
        202:
          description: "Accepted - request is being processed in the background"
        201:
          description: "Created - specified amount of ETH is ready for deposit"
          content:
            "application/json":
              schema:
                title: ListStakeResponse
                type: array
                items:
                  type: "object"
                  properties:
                    pubkey:
                      type: string
                    signature:
                      type: string
                    withdrawal_credentials:
                      type: string
                    deposit_data_root:
                      type: string
                    fee_recipient:
                      type: string
                  required:
                    - pubkey
                    - signature
                    - withdrawal_credentials
                    - deposit_data_root
                    - fee_recipient
        400:
          description: "Specified fee recipient or withdrawal credentials follow incorrect pattern or amount of ETH is invalid i.e. must be at least 32, a multiple of 32 and less than 8000."
        428:
          description: "Staking capacity limit reached. Contact Chorus One to increase capacity."
  /api/v1/unstake:
    post:
      description: "Unstake x amounts of 32ETH on Ethereum"
      tags:
        - staking
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              title: "UnstakeRequest"
              type: "object"
              properties:
                amount:
                  type: integer
                  format: integer
                  description: "Amount of ETH to be unstaked, should be in multiples of 32ETH"
                public_signing_keys:
                  type: "array"
                  items:
                    type: string
                  description: "Alternatively users may specify which validators they wish to exit"
                network:
                  type: string
                  enum: ["ethereum"]
                  description: "Network for which to perform unstaking request"
                request_id:
                  type: "string"
                  format: "uuid"
                  description: "Unstake request id used to track request when polling the endpoint."
              required:
                - network
                - request_id
      responses:
        202:
          description: "Accepted - request is being processed in the background"
        200:
          description: "Received successful response from Beacon chain for exiting specified amount of ETH. It takes at a minimum 5 epochs (32 minutes) for a validator to exit after initiating a voluntary exit. This number can be much higher depending on how many other validators are queued to exit."
        400:
          description: "Specified amount of ETH is invalid."
        428:
          description: "Specified amount of ETH is not yet in a state available to be unstaked. The amount of ETH should be actively staking, not have been slashed and have been active for at least 256 epochs (~27 hours) since it has been activated to be eligible for unstaking."
  /api/v1/staking_requests:
    get:
      summary: Get info on staking requests made via OPUS API
      tags:
        - staking
      parameters:
        - in: query
          name: request_ids
          schema:
            type: array
            items: 
              type: string
              format: uuid
          required: false
          description: "request_id of the staking_request"
      responses:
        200:
          description: "Staking Requests grouped by request_id, optionally can be filtered by request_id, by default all will be returned."
          content:
            "application/json":
              schema:
                title: StakingRequestsResponse
                $ref: "#components/schemas/StakingRequestsResponse"
        404: 
          description: "Staking request not found"
  /api/v1/stats:
    post:
      description: "Get rewards and aggregated node statistics for validators hosted by Chorus/Opus"
      tags:
        - rewards & performance
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              $ref: "#/components/schemas/StatsRequest"
      responses:
        200:
          description: "Reward and performance data have been returned, to this day"
          content:
            "application/json":
              schema:
                title: StatsFullResponse
                type: "object"
                properties:
                  rewards:
                    $ref: "#/components/schemas/RewardsAndEffectivenessResponse"
                  nodes:
                    $ref: "#/components/schemas/NodeAggregateResponse"
                  continue_query:
                    $ref: "#/components/schemas/StatsRequest"
                required:
                  - rewards
                  - nodes

  /api/v1/validators:
    post:
      description: "Get rewards and aggregated node statistics for validators hosted by Chorus/Opus"
      tags:
        - rewards & performance
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              $ref: "#/components/schemas/ValidatorsRequest"
      responses:
        200:
          description: "Reward and performance data have been returned, to this day"
          content:
            "application/json":
              schema:
                title: ValidatorsFullResponse
                $ref: "#components/schemas/ValidatorsResponse"
  /api/v1/signed-exit-transactions:
    post:
      description: "Pregenerate signed exit transaction for previously created validators via OPUS API, customers can opt-in to encrypt the response with a PGP public key shared previously."
      tags:
        - staking
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              title: "EthereumSignedExitTransactionRequest"
              type: "object"
              properties:
                public_keys:
                  type: array
                  items:
                    type: "string"
                    description: |
                      Validator signing key
                  description: |
                    Array of public signing key,
                    if empty returns all validators
              required:
                - encryption
      responses:
        200:
          description: OK
          content:
            "application/json":
              schema:
                title: ValidatorExitResponses
                type: "object"
                properties:
                  exit_transactions:
                    type: array
                    items:
                      type: "string"
                      pattern: "^0x[a-fA-F0-9]*$"
                      description: "hex encoded exit transaction bytes"
                      example: |
                        example signature data:
                        {
                          "exit":{
                            "message":{
                              "epoch":"38878",
                              "validator_index":"125689"
                            },
                            "signature":"0x84f21a5156596e3028e22e
                              7cc7de56a550734994171dde6fdd6cf
                              98edbe5ada2124f9a974be2b73b2f1c99
                              fab03324a1f90338c5ea5a73bb3c53598
                              e276502ac17f37062d4bc84f1c3d714e3
                              b33f48e6c34dec961e638ffd48e21d734fec0fdc"
                          }
                        }
                  fork_version:
                    type: "string"
                    pattern: "^0x[a-fA-F0-9]*$"
                    description: "hex encoded current fork version of the beacon_chain (at the time of this request)"
                required:
                  - exit_transaction
                  - fork_version
        400:
          description: "Bad request"
          content:
            "text/plain":
              schema:
                type: "string"
                description: "Error message describing issue with the request. Errors include: 'No PGP public key supplied'"
        404:
          description: "Validator not found"