openapi: 3.1.0
info:
  title: MessageHop REST API
  version: 1.2.0
  description: MessageHop sessions, Customer control, and Registered App data-plane API.
servers:
  - url: https://msghop.com
security:
  - serverCredential: []
paths:
  /v1/sessions:
    post:
      operationId: createSession
      summary: Create a short-lived MessageHop user session.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: "#/components/schemas/SessionRequest"}
      responses:
        "200": {description: Active MessageHop user session}
        "401": {$ref: "#/components/responses/Error"}
        "429": {$ref: "#/components/responses/Error"}
  /v1/sessions/refresh:
    post:
      operationId: refreshSession
      summary: Refresh a MessageHop user session.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [refreshToken]
              properties:
                refreshToken: {type: string, minLength: 10}
      responses:
        "200": {description: Refreshed MessageHop user session}
        "401": {$ref: "#/components/responses/Error"}
  /v1/password-recovery:
    post:
      operationId: requestPasswordRecovery
      summary: Request a non-enumerating recovery email.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [email]
              properties:
                email: {type: string, format: email, maxLength: 320}
      responses:
        "202": {description: Recovery request accepted}
  /v1/control/{operation}:
    post:
      operationId: invokeCustomerControlOperation
      summary: Invoke an authorized Customer control operation.
      security:
        - userAccessToken: []
      parameters:
        - name: operation
          in: path
          required: true
          schema: {$ref: "#/components/schemas/CustomerControlOperation"}
        - $ref: "#/components/parameters/RequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema: {type: object, maxProperties: 64}
      responses:
        "200": {description: Direct operation result}
        "400": {$ref: "#/components/responses/Error"}
        "401": {$ref: "#/components/responses/Error"}
        "403": {$ref: "#/components/responses/Error"}
        "404": {$ref: "#/components/responses/Error"}
  /v1/auth/check:
    get:
      operationId: checkServerCredential
      summary: Verify a credential and return its effective scope and grants.
      responses:
        "200": {description: Active Registered App scope}
        "401": {$ref: "#/components/responses/Error"}
        "403": {$ref: "#/components/responses/Error"}
  /v1/messages:
    post:
      operationId: acceptMessage
      summary: Durably accept one message for asynchronous dispatch.
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
        - $ref: "#/components/parameters/RequestId"
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: "#/components/schemas/MessageRequest"}
      responses:
        "202": {description: Accepted}
        "400": {$ref: "#/components/responses/Error"}
        "401": {$ref: "#/components/responses/Error"}
        "403": {$ref: "#/components/responses/Error"}
        "409": {$ref: "#/components/responses/Error"}
        "503": {$ref: "#/components/responses/Error"}
  /v1/messages/{messageId}:
    get:
      operationId: getMessage
      summary: Read one app-scoped message projection.
      parameters:
        - name: messageId
          in: path
          required: true
          schema: {type: string}
      responses:
        "200": {description: Customer-safe projection}
        "401": {$ref: "#/components/responses/Error"}
        "403": {$ref: "#/components/responses/Error"}
        "404": {$ref: "#/components/responses/Error"}
  /v1/balances:
    get:
      operationId: getBalance
      summary: Read an Organization balance projection.
      parameters:
        - name: currency
          in: query
          required: true
          schema: {type: string, pattern: "^[A-Z]{3}$"}
      responses:
        "200": {description: Balance projection}
        "401": {$ref: "#/components/responses/Error"}
        "403": {$ref: "#/components/responses/Error"}
  /v1/usage:
    get:
      operationId: listUsage
      summary: Read bounded app-scoped financial activity.
      parameters:
        - name: currency
          in: query
          required: true
          schema: {type: string, pattern: "^[A-Z]{3}$"}
        - name: from
          in: query
          required: true
          schema: {type: integer, format: int64}
        - name: to
          in: query
          required: true
          schema: {type: integer, format: int64}
        - name: limit
          in: query
          schema: {type: integer, minimum: 1, maximum: 100, default: 50}
        - name: cursor
          in: query
          schema: {type: string}
      responses:
        "200": {description: Usage page}
        "401": {$ref: "#/components/responses/Error"}
        "403": {$ref: "#/components/responses/Error"}
  /v1/health:
    get:
      operationId: getHealth
      security: []
      responses:
        "200": {description: Healthy}
  /v1/readiness:
    get:
      operationId: getReadiness
      security: []
      responses:
        "200": {description: Ready}
        "503": {description: Not ready}
  /v1/release:
    get:
      operationId: getRelease
      security: []
      responses:
        "200": {description: Release descriptor}
components:
  securitySchemes:
    userAccessToken:
      type: http
      scheme: bearer
      bearerFormat: MessageHop user access token
    serverCredential:
      type: http
      scheme: bearer
      bearerFormat: mh_<environment>_<selector>_<secret>
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      schema: {type: string, minLength: 8, maxLength: 128}
    RequestId:
      name: X-Request-Id
      in: header
      required: false
      schema: {type: string, minLength: 2, maxLength: 128}
  schemas:
    SessionRequest:
      type: object
      additionalProperties: false
      required: [email, password]
      properties:
        email: {type: string, format: email, maxLength: 320}
        password: {type: string, format: password}
    CustomerControlOperation:
      type: string
      enum:
        - acceptOrganizationInvitation
        - appendCustomerSupportReply
        - closeChat
        - createChatRequest
        - createOrganization
        - createServerCredential
        - createSupportTicket
        - escalateChatToTicket
        - generateAttachmentUploadUrl
        - getClientReleasePolicy
        - getCustomerBalance
        - getCustomerContext
        - getCustomerSupportTicket
        - getPlatformConfig
        - getTicketAttachmentDownloadUrl
        - inviteOrganizationMember
        - listChatMessages
        - listCustomerBillingActivity
        - listCustomerMessageHistory
        - listCustomerSupportTickets
        - listOrganizationBudgets
        - listOrganizationMembers
        - listServerCredentials
        - registerApp
        - registerTicketAttachment
        - revokeOrganizationMember
        - revokeServerCredential
        - rotateServerCredential
        - saveOrganizationBudget
        - sendChatMessage
    MessageRequest:
      type: object
      additionalProperties: false
      required: [projectId, appId, channel, to, templateId, variables]
      properties:
        projectId: {type: string}
        appId: {type: string}
        channel: {enum: [sms, email, whatsapp]}
        to: {type: string}
        templateId: {type: string}
        variables:
          type: object
          additionalProperties: {type: string, maxLength: 500}
          maxProperties: 50
        clientReference: {type: string, maxLength: 128}
    Error:
      type: object
      required: [code, message, requestId, retryable, fieldErrors]
      properties:
        code: {type: string}
        message: {type: string}
        requestId: {type: string}
        retryable: {type: boolean}
        fieldErrors: {type: array, items: {type: object}}
  responses:
    Error:
      description: Stable redacted error
      content:
        application/json:
          schema: {$ref: "#/components/schemas/Error"}
