# Copyright (c) Abstract Machines
# SPDX-License-Identifier: Apache-2.0

openapi: 3.0.1
info:
  title: Magistrala Bootstrap service
  description: |
    HTTP API for managing platform clients configuration, device profiles,
    and enrollment bindings.
    Some useful links:
    - [The Magistrala repository](https://github.com/absmach/magistrala)
  contact:
    email: info@absmach.eu
  license:
    name: Apache 2.0
    url: https://github.com/absmach/magistrala/blob/main/LICENSE
  version: 0.19.0

servers:
  - url: http://localhost:9013
  - url: https://localhost:9013

tags:
  - name: configs
    description: Bootstrap enrollment configurations
    externalDocs:
      description: Find out more about Configs
      url: https://magistrala.absmach.eu/docs/
  - name: profiles
    description: Device configuration profiles and templates
  - name: enrollments
    description: Enrollment profile assignment and resource bindings

paths:
  # ── Enrollment configs ────────────────────────────────────────────────────

  /{domainID}/clients/configs:
    post:
      operationId: createConfig
      summary: Adds a new enrollment config
      description: |
        Adds a new bootstrap enrollment config owned by the user identified by
        the provided access token.
      tags:
        - configs
      parameters:
        - $ref: "#/components/parameters/DomainID"
      requestBody:
        $ref: "#/components/requestBodies/ConfigCreateReq"
      responses:
        "201":
          $ref: "#/components/responses/ConfigCreateRes"
        "400":
          description: Failed due to malformed JSON.
        "401":
          description: Missing or invalid access token provided.
        "403":
          description: Failed to perform authorization over the entity.
        "409":
          description: Failed due to using an existing external ID.
        "415":
          description: Missing or invalid content type.
        "500":
          $ref: "#/components/responses/ServiceError"
    get:
      operationId: getConfigs
      summary: Retrieves managed configs
      description: |
        Retrieves a paginated list of managed enrollment configs.
      tags:
        - configs
      parameters:
        - $ref: "#/components/parameters/DomainID"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
        - $ref: "#/components/parameters/Status"
        - $ref: "#/components/parameters/Name"
      responses:
        "200":
          $ref: "#/components/responses/ConfigListRes"
        "400":
          description: Failed due to malformed query parameters.
        "401":
          description: Missing or invalid access token provided.
        "500":
          $ref: "#/components/responses/ServiceError"

  /{domainID}/clients/configs/{configID}:
    get:
      operationId: getConfig
      summary: Retrieves a config
      tags:
        - configs
      parameters:
        - $ref: "#/components/parameters/DomainID"
        - $ref: "#/components/parameters/ConfigID"
      responses:
        "200":
          $ref: "#/components/responses/ConfigRes"
        "401":
          description: Missing or invalid access token provided.
        "403":
          description: Failed to perform authorization over the entity.
        "404":
          description: Config does not exist.
        "500":
          $ref: "#/components/responses/ServiceError"
    patch:
      operationId: updateConfig
      summary: Updates config info
      description: |
        Updates name and content of an existing enrollment config.
      tags:
        - configs
      parameters:
        - $ref: "#/components/parameters/DomainID"
        - $ref: "#/components/parameters/ConfigID"
      requestBody:
        $ref: "#/components/requestBodies/ConfigUpdateReq"
      responses:
        "200":
          description: Config updated.
        "400":
          description: Failed due to malformed JSON.
        "401":
          description: Missing or invalid access token provided.
        "403":
          description: Failed to perform authorization over the entity.
        "404":
          description: Config does not exist.
        "415":
          description: Missing or invalid content type.
        "500":
          $ref: "#/components/responses/ServiceError"
    delete:
      operationId: removeConfig
      summary: Removes a config
      tags:
        - configs
      parameters:
        - $ref: "#/components/parameters/DomainID"
        - $ref: "#/components/parameters/ConfigID"
      responses:
        "204":
          description: Config removed.
        "401":
          description: Missing or invalid access token provided.
        "403":
          description: Failed to perform authorization over the entity.
        "500":
          $ref: "#/components/responses/ServiceError"

  /{domainID}/clients/configs/certs/{configID}:
    patch:
      operationId: updateConfigCerts
      summary: Updates client certificates
      description: |
        Replaces the certificate fields (client_cert, client_key, ca_cert) of an
        existing enrollment config.
      tags:
        - configs
      parameters:
        - $ref: "#/components/parameters/DomainID"
        - $ref: "#/components/parameters/ConfigID"
      requestBody:
        $ref: "#/components/requestBodies/ConfigCertUpdateReq"
      responses:
        "200":
          $ref: "#/components/responses/ConfigUpdateCertsRes"
        "400":
          description: Failed due to malformed JSON.
        "401":
          description: Missing or invalid access token provided.
        "403":
          description: Failed to perform authorization over the entity.
        "404":
          description: Config does not exist.
        "415":
          description: Missing or invalid content type.
        "500":
          $ref: "#/components/responses/ServiceError"

  /{domainID}/clients/configs/{configID}/enable:
    post:
      operationId: enableConfig
      summary: Enables a config
      description: |
        Enables the enrollment config, allowing its device to bootstrap.
        Idempotent — returns the current config without error if already enabled.
      tags:
        - configs
      parameters:
        - $ref: "#/components/parameters/DomainID"
        - $ref: "#/components/parameters/ConfigID"
      responses:
        "200":
          $ref: "#/components/responses/ConfigRes"
        "401":
          description: Missing or invalid access token provided.
        "403":
          description: Failed to perform authorization over the entity.
        "404":
          description: Config does not exist.
        "500":
          $ref: "#/components/responses/ServiceError"

  /{domainID}/clients/configs/{configID}/disable:
    post:
      operationId: disableConfig
      summary: Disables a config
      description: |
        Disables the enrollment config, preventing its device from bootstrapping.
        Idempotent — returns the current config without error if already disabled.
      tags:
        - configs
      parameters:
        - $ref: "#/components/parameters/DomainID"
        - $ref: "#/components/parameters/ConfigID"
      responses:
        "200":
          $ref: "#/components/responses/ConfigRes"
        "401":
          description: Missing or invalid access token provided.
        "403":
          description: Failed to perform authorization over the entity.
        "404":
          description: Config does not exist.
        "500":
          $ref: "#/components/responses/ServiceError"

  # ── Device bootstrap ──────────────────────────────────────────────────────

  /clients/bootstrap/{externalID}:
    get:
      operationId: getBootstrapConfig
      summary: Retrieves a bootstrap configuration
      description: |
        Returns the rendered bootstrap configuration for a device identified by
        its external ID and external key.
      tags:
        - configs
      security:
        - bootstrapAuth: []
      parameters:
        - $ref: "#/components/parameters/ExternalID"
      responses:
        "200":
          $ref: "#/components/responses/BootstrapConfigRes"
        "400":
          description: Missing or invalid external ID.
        "401":
          description: Missing or invalid external key.
        "403":
          description: Config is disabled.
        "404":
          description: No config found for the given external ID.
        "500":
          $ref: "#/components/responses/ServiceError"

  /clients/bootstrap/secure/{externalID}:
    get:
      operationId: getSecureBootstrapConfig
      summary: Retrieves an encrypted bootstrap configuration
      description: |
        Returns the rendered bootstrap configuration encrypted with the device's
        AES key. The external key must be hex-encoded and AES-encrypted.
      tags:
        - configs
      security:
        - bootstrapEncAuth: []
      parameters:
        - $ref: "#/components/parameters/ExternalID"
      responses:
        "200":
          description: Encrypted bootstrap payload (binary).
          content:
            application/octet-stream:
              schema:
                type: string
                format: binary
        "400":
          description: Missing or invalid external ID.
        "401":
          description: Missing or invalid encrypted external key.
        "403":
          description: Config is disabled.
        "404":
          description: No config found for the given external ID.
        "500":
          $ref: "#/components/responses/ServiceError"

  # ── Device profiles ───────────────────────────────────────────────────────

  /{domainID}/clients/bootstrap/profiles:
    post:
      operationId: createProfile
      summary: Creates a device profile
      tags:
        - profiles
      parameters:
        - $ref: "#/components/parameters/DomainID"
      requestBody:
        $ref: "#/components/requestBodies/ProfileCreateReq"
      responses:
        "201":
          $ref: "#/components/responses/ProfileRes"
        "400":
          description: Failed due to malformed JSON.
        "401":
          description: Missing or invalid access token provided.
        "403":
          description: Failed to perform authorization over the entity.
        "409":
          description: A profile with the same name already exists in this domain.
        "415":
          description: Missing or invalid content type.
        "500":
          $ref: "#/components/responses/ServiceError"
    get:
      operationId: listProfiles
      summary: Lists device profiles
      tags:
        - profiles
      parameters:
        - $ref: "#/components/parameters/DomainID"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Offset"
      responses:
        "200":
          $ref: "#/components/responses/ProfileListRes"
        "400":
          description: Failed due to malformed query parameters.
        "401":
          description: Missing or invalid access token provided.
        "500":
          $ref: "#/components/responses/ServiceError"

  /{domainID}/clients/bootstrap/profiles/upload:
    post:
      operationId: uploadProfile
      summary: Uploads a device profile
      description: |
        Uploads a profile definition. Accepts JSON, YAML, or TOML payloads
        depending on the Content-Type header.
      tags:
        - profiles
      parameters:
        - $ref: "#/components/parameters/DomainID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Profile"
          application/yaml:
            schema:
              $ref: "#/components/schemas/Profile"
          application/toml:
            schema:
              $ref: "#/components/schemas/Profile"
      responses:
        "201":
          $ref: "#/components/responses/ProfileRes"
        "400":
          description: Failed due to malformed body.
        "401":
          description: Missing or invalid access token provided.
        "415":
          description: Unsupported content type.
        "500":
          $ref: "#/components/responses/ServiceError"

  /{domainID}/clients/bootstrap/profiles/{profileID}:
    get:
      operationId: getProfile
      summary: Retrieves a device profile
      tags:
        - profiles
      parameters:
        - $ref: "#/components/parameters/DomainID"
        - $ref: "#/components/parameters/ProfileID"
      responses:
        "200":
          $ref: "#/components/responses/ProfileRes"
        "401":
          description: Missing or invalid access token provided.
        "404":
          description: Profile does not exist.
        "500":
          $ref: "#/components/responses/ServiceError"
    patch:
      operationId: updateProfile
      summary: Updates a device profile
      tags:
        - profiles
      parameters:
        - $ref: "#/components/parameters/DomainID"
        - $ref: "#/components/parameters/ProfileID"
      requestBody:
        $ref: "#/components/requestBodies/ProfileUpdateReq"
      responses:
        "200":
          $ref: "#/components/responses/ProfileRes"
        "400":
          description: Failed due to malformed JSON.
        "401":
          description: Missing or invalid access token provided.
        "404":
          description: Profile does not exist.
        "415":
          description: Missing or invalid content type.
        "500":
          $ref: "#/components/responses/ServiceError"
    delete:
      operationId: deleteProfile
      summary: Deletes a device profile
      tags:
        - profiles
      parameters:
        - $ref: "#/components/parameters/DomainID"
        - $ref: "#/components/parameters/ProfileID"
      responses:
        "204":
          description: Profile deleted.
        "401":
          description: Missing or invalid access token provided.
        "404":
          description: Profile does not exist.
        "500":
          $ref: "#/components/responses/ServiceError"

  /{domainID}/clients/bootstrap/profiles/{profileID}/slots:
    get:
      operationId: getProfileSlots
      summary: Retrieves the binding slots declared by a profile
      tags:
        - profiles
      parameters:
        - $ref: "#/components/parameters/DomainID"
        - $ref: "#/components/parameters/ProfileID"
      responses:
        "200":
          description: Binding slots retrieved.
          content:
            application/json:
              schema:
                type: object
                properties:
                  slots:
                    type: array
                    items:
                      $ref: "#/components/schemas/BindingSlot"
        "401":
          description: Missing or invalid access token provided.
        "404":
          description: Profile does not exist.
        "500":
          $ref: "#/components/responses/ServiceError"

  /{domainID}/clients/bootstrap/profiles/{profileID}/render-preview:
    post:
      operationId: renderPreview
      summary: Renders a profile template preview
      description: |
        Renders the profile's content template with the supplied device context
        and binding snapshots. No external service calls are made — all data
        must be supplied in the request body.
      tags:
        - profiles
      parameters:
        - $ref: "#/components/parameters/DomainID"
        - $ref: "#/components/parameters/ProfileID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                config:
                  $ref: "#/components/schemas/Config"
                render_context:
                  type: object
                  additionalProperties: true
                  description: Per-device variable overrides.
                bindings:
                  type: array
                  items:
                    $ref: "#/components/schemas/BindingSnapshot"
      responses:
        "200":
          description: Rendered template output.
          content:
            application/json:
              schema:
                type: object
                properties:
                  rendered:
                    type: string
        "400":
          description: Template rendering failed.
        "401":
          description: Missing or invalid access token provided.
        "404":
          description: Profile does not exist.
        "500":
          $ref: "#/components/responses/ServiceError"

  # ── Enrollment bindings ───────────────────────────────────────────────────

  /{domainID}/clients/bootstrap/enrollments/{configID}/profile:
    patch:
      operationId: assignProfile
      summary: Assigns a profile to an enrollment
      tags:
        - enrollments
      parameters:
        - $ref: "#/components/parameters/DomainID"
        - $ref: "#/components/parameters/ConfigID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                profile_id:
                  type: string
                  format: uuid
              required:
                - profile_id
      responses:
        "200":
          description: Profile assigned.
        "400":
          description: Failed due to malformed JSON.
        "401":
          description: Missing or invalid access token provided.
        "404":
          description: Config or profile does not exist.
        "415":
          description: Missing or invalid content type.
        "500":
          $ref: "#/components/responses/ServiceError"

  /{domainID}/clients/bootstrap/enrollments/{configID}/bindings:
    put:
      operationId: bindResources
      summary: Binds resources to profile slots
      description: |
        Resolves the requested resource bindings via their owning services,
        stores snapshots, and marks the enrollment renderable when all required
        slots are satisfied.
      tags:
        - enrollments
      parameters:
        - $ref: "#/components/parameters/DomainID"
        - $ref: "#/components/parameters/ConfigID"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                bindings:
                  type: array
                  minItems: 1
                  items:
                    $ref: "#/components/schemas/BindingRequest"
              required:
                - bindings
      responses:
        "200":
          description: Bindings stored.
        "400":
          description: Failed due to malformed JSON or invalid binding request.
        "401":
          description: Missing or invalid access token provided.
        "404":
          description: Config or resource not found.
        "415":
          description: Missing or invalid content type.
        "500":
          $ref: "#/components/responses/ServiceError"
    get:
      operationId: listBindings
      summary: Lists binding snapshots for an enrollment
      tags:
        - enrollments
      parameters:
        - $ref: "#/components/parameters/DomainID"
        - $ref: "#/components/parameters/ConfigID"
      responses:
        "200":
          $ref: "#/components/responses/BindingsRes"
        "401":
          description: Missing or invalid access token provided.
        "404":
          description: Config does not exist.
        "500":
          $ref: "#/components/responses/ServiceError"

  /{domainID}/clients/bootstrap/enrollments/{configID}/bindings/refresh:
    post:
      operationId: refreshBindings
      summary: Refreshes all binding snapshots
      description: |
        Re-resolves every existing binding for the enrollment and updates the
        stored snapshots with current resource data.
      tags:
        - enrollments
      parameters:
        - $ref: "#/components/parameters/DomainID"
        - $ref: "#/components/parameters/ConfigID"
      responses:
        "200":
          description: Bindings refreshed.
        "401":
          description: Missing or invalid access token provided.
        "404":
          description: Config does not exist.
        "500":
          $ref: "#/components/responses/ServiceError"

  # ── Health ────────────────────────────────────────────────────────────────

  /health:
    get:
      summary: Retrieves service health check info.
      tags:
        - health
      security: []
      responses:
        "200":
          $ref: "#/components/responses/HealthRes"
        "500":
          $ref: "#/components/responses/ServiceError"

components:
  schemas:
    Status:
      type: string
      enum: [enabled, disabled]
      description: Enrollment status.

    Config:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Enrollment ID.
        domain_id:
          type: string
          format: uuid
          description: Domain the enrollment belongs to.
        external_id:
          type: string
          description: External device identifier (e.g. MAC address).
        external_key:
          type: string
          description: External authentication key (write-only; not returned after creation).
        name:
          type: string
          description: Human-readable name.
        content:
          type: string
          description: Legacy free-form configuration payload.
        status:
          $ref: "#/components/schemas/Status"
        profile_id:
          type: string
          format: uuid
          description: ID of the device profile used for template rendering.
        render_context:
          type: object
          additionalProperties: true
          description: Per-device variable values injected into the profile template.
        client_cert:
          type: string
          description: Client TLS certificate (PEM).
        client_key:
          type: string
          description: Client TLS private key (PEM).
        ca_cert:
          type: string
          description: CA certificate (PEM).
      required:
        - external_id
        - external_key

    ConfigList:
      type: object
      properties:
        total:
          type: integer
          minimum: 0
        offset:
          type: integer
          minimum: 0
          default: 0
        limit:
          type: integer
          maximum: 100
          default: 10
        configs:
          type: array
          items:
            $ref: "#/components/schemas/Config"
      required:
        - configs

    BootstrapConfig:
      type: object
      description: Rendered configuration returned to a bootstrapping device.
      properties:
        id:
          type: string
          format: uuid
          description: Enrollment ID.
        content:
          type: string
          description: Rendered configuration payload.
        client_cert:
          type: string
          description: Client TLS certificate (PEM).
        client_key:
          type: string
          description: Client TLS private key (PEM).
        ca_cert:
          type: string
          description: CA certificate (PEM).

    ConfigUpdateCerts:
      type: object
      properties:
        id:
          type: string
          format: uuid
        client_cert:
          type: string
        client_key:
          type: string
        ca_cert:
          type: string
        domain_id:
          type: string
          format: uuid

    Profile:
      type: object
      properties:
        id:
          type: string
          format: uuid
          readOnly: true
        domain_id:
          type: string
          format: uuid
          readOnly: true
        name:
          type: string
        description:
          type: string
        template_format:
          type: string
          enum: [go-template, raw]
          default: go-template
        content_template:
          type: string
          description: Template text rendered at bootstrap time.
        defaults:
          type: object
          additionalProperties: true
          description: Default variable values merged with per-device render_context.
        binding_slots:
          type: array
          items:
            $ref: "#/components/schemas/BindingSlot"
          description: Named resource slots declared by this profile.
        version:
          type: integer
          readOnly: true
        created_at:
          type: string
          format: date-time
          readOnly: true
        updated_at:
          type: string
          format: date-time
          readOnly: true
      required:
        - name
        - template_format

    ProfileList:
      type: object
      properties:
        total:
          type: integer
          minimum: 0
        offset:
          type: integer
          minimum: 0
        limit:
          type: integer
          maximum: 100
        profiles:
          type: array
          items:
            $ref: "#/components/schemas/Profile"

    BindingSlot:
      type: object
      description: A named resource placeholder declared by a profile.
      properties:
        name:
          type: string
          description: Slot name referenced in the template (e.g. "mqtt_client").
        type:
          type: string
          enum: [client, channel, cert]
          description: Expected resource type.
        required:
          type: boolean
          default: true
          description: Whether the slot must be bound before the device can bootstrap.
        fields:
          type: array
          items:
            type: string
          description: Resource fields snapshotted at binding time.
      required:
        - name
        - type

    BindingRequest:
      type: object
      description: A request to bind a profile slot to a concrete resource.
      properties:
        slot:
          type: string
          description: Profile slot name to fill.
        type:
          type: string
          enum: [client, channel, cert]
        resource_id:
          type: string
          format: uuid
          description: ID of the resource in its owning service.
      required:
        - slot
        - type
        - resource_id

    BindingSnapshot:
      type: object
      description: Bootstrap-owned snapshot of a bound resource.
      properties:
        config_id:
          type: string
          format: uuid
        slot:
          type: string
        type:
          type: string
        resource_id:
          type: string
          format: uuid
        snapshot:
          type: object
          additionalProperties: true
          description: Non-secret resource fields captured at binding time.
        updated_at:
          type: string
          format: date-time

  parameters:
    ConfigID:
      name: configID
      description: Unique enrollment config identifier.
      in: path
      schema:
        type: string
        format: uuid
      required: true

    ProfileID:
      name: profileID
      description: Unique profile identifier.
      in: path
      schema:
        type: string
        format: uuid
      required: true

    ExternalID:
      name: externalID
      description: External device identifier provided at registration.
      in: path
      schema:
        type: string
      required: true

    DomainID:
      name: domainID
      description: Unique domain identifier.
      in: path
      schema:
        type: string
        format: uuid
      required: true
      example: bb7edb32-2eac-4aad-aebe-ed96fe073879

    Limit:
      name: limit
      description: Maximum number of items to return.
      in: query
      schema:
        type: integer
        default: 10
        maximum: 100
        minimum: 1
      required: false

    Offset:
      name: offset
      description: Number of items to skip.
      in: query
      schema:
        type: integer
        default: 0
        minimum: 0
      required: false

    Status:
      name: status
      description: Filter by enrollment status.
      in: query
      schema:
        $ref: "#/components/schemas/Status"
      required: false

    Name:
      name: name
      description: Filter by name (partial, case-insensitive match).
      in: query
      schema:
        type: string
      required: false

  requestBodies:
    ConfigCreateReq:
      description: New enrollment config.
      required: true
      content:
        application/json:
          schema:
            type: object
            properties:
              external_id:
                type: string
              external_key:
                type: string
              name:
                type: string
              content:
                type: string
              profile_id:
                type: string
                format: uuid
              render_context:
                type: object
                additionalProperties: true
              client_cert:
                type: string
              client_key:
                type: string
              ca_cert:
                type: string
            required:
              - external_id
              - external_key

    ConfigUpdateReq:
      description: Fields to update on an enrollment config.
      required: true
      content:
        application/json:
          schema:
            type: object
            properties:
              name:
                type: string
              content:
                type: string

    ConfigCertUpdateReq:
      description: Replacement certificate fields.
      required: true
      content:
        application/json:
          schema:
            type: object
            properties:
              client_cert:
                type: string
              client_key:
                type: string
              ca_cert:
                type: string

    ProfileCreateReq:
      description: New device profile.
      required: true
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Profile"

    ProfileUpdateReq:
      description: Updated device profile fields. All fields are optional.
      required: true
      content:
        application/json:
          schema:
            type: object
            properties:
              name:
                type: string
              description:
                type: string
              template_format:
                type: string
                enum: [go-template, raw]
              content_template:
                type: string
              defaults:
                type: object
                additionalProperties: true
              binding_slots:
                type: array
                items:
                  $ref: "#/components/schemas/BindingSlot"

  responses:
    ConfigCreateRes:
      description: Config registered.
      headers:
        Location:
          schema:
            type: string
          description: Relative URL of the created config (e.g. /clients/configs/{configID}).
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Config"

    ConfigListRes:
      description: Configs retrieved.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ConfigList"

    ConfigRes:
      description: Config retrieved.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Config"

    ConfigUpdateCertsRes:
      description: Config certificates updated.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ConfigUpdateCerts"

    BootstrapConfigRes:
      description: |
        Rendered bootstrap configuration. For the secure endpoint the response
        body is AES-encrypted binary rather than JSON.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/BootstrapConfig"

    ProfileRes:
      description: Profile retrieved or created.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Profile"

    ProfileListRes:
      description: Profiles retrieved.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ProfileList"

    BindingsRes:
      description: Binding snapshots retrieved.
      content:
        application/json:
          schema:
            type: object
            properties:
              bindings:
                type: array
                items:
                  $ref: "#/components/schemas/BindingSnapshot"

    ServiceError:
      description: Unexpected server-side error occurred.

    HealthRes:
      description: Service health check.
      content:
        application/health+json:
          schema:
            $ref: "./schemas/health_info.yaml"

  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        User access token: "Authorization: Bearer <user_token>"

    bootstrapAuth:
      type: http
      scheme: bearer
      bearerFormat: string
      description: |
        Device access: "Authorization: Client <external_key>"

    bootstrapEncAuth:
      type: http
      scheme: bearer
      bearerFormat: aes-uuid
      description: |
        Device access with AES-encrypted key: "Authorization: Client <external_enc_key>"
        The external key must be hex-encoded and encrypted with AES using the
        SHA256 of the external key itself as the encryption key.

security:
  - bearerAuth: []
