openapi: 3.0.3
info:
  title: ModuleCrafter WA API
  version: 4.0.0
  description: Worldwide multi-tenant WhatsApp gateway API with E.164/ISO-country phone normalization, media/PDF sending, commercial entitlements and a restricted WHMCS Sender interface.
servers:
  - url: https://wa.modulecrafter.com/api
security:
  - bearerAuth: []
paths:
  /v1/health:
    get:
      summary: Customer API health
      responses:
        '200': { description: OK }
  /v1/me:
    get:
      summary: Current account entitlement and usage
      responses:
        '200': { description: OK }
  /v2/usage:
    get:
      summary: Current billing-period message quota
      responses:
        '200': { description: OK }
        '402': { description: No active subscription }
  /v1/channels:
    get:
      summary: List channels
      responses:
        '200': { description: OK }
    post:
      summary: Create a Linked Device or Meta Cloud API channel
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [provider, name]
              properties:
                provider: { type: string, enum: [qr, meta_cloud] }
                name: { type: string }
                waba_id: { type: string }
                phone_number_id: { type: string }
                access_token: { type: string }
                verify_token: { type: string }
                app_secret: { type: string }
      responses:
        '201': { description: Created }
        '403': { description: Provider not allowed by plan }
        '409': { description: Channel limit reached }
  /v1/channels/{id}:
    patch:
      summary: Update Meta channel credentials
      parameters: [ { $ref: '#/components/parameters/Id' } ]
      responses: { '200': { description: Updated } }
    delete:
      summary: Disable channel
      parameters: [ { $ref: '#/components/parameters/Id' } ]
      responses: { '204': { description: Disabled } }
  /v1/channels/{id}/connect:
    post:
      summary: Start/restore Linked Device connection
      parameters: [ { $ref: '#/components/parameters/Id' } ]
      responses: { '200': { description: OK } }
  /v1/channels/{id}/qr:
    get:
      summary: Current QR/pairing state
      parameters: [ { $ref: '#/components/parameters/Id' } ]
      responses: { '200': { description: OK } }
  /v1/channels/{id}/pairing-code:
    post:
      summary: Request phone-number pairing code
      parameters: [ { $ref: '#/components/parameters/Id' } ]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [phone]
              properties:
                phone: { type: string }
      responses: { '200': { description: OK } }
  /v1/channels/{id}/disconnect:
    post:
      summary: Disconnect Linked Device
      parameters: [ { $ref: '#/components/parameters/Id' } ]
      responses: { '200': { description: OK } }
  /v1/channels/{id}/test:
    post:
      summary: Verify Official Meta channel
      parameters: [ { $ref: '#/components/parameters/Id' } ]
      responses: { '200': { description: OK } }
  /v1/channels/{id}/templates:
    get:
      summary: List synced Meta templates
      parameters: [ { $ref: '#/components/parameters/Id' } ]
      responses: { '200': { description: OK } }
  /v1/channels/{id}/templates/sync:
    post:
      summary: Sync Meta templates
      parameters: [ { $ref: '#/components/parameters/Id' } ]
      responses: { '200': { description: OK } }
  /v1/messages:
    get:
      summary: List messages
      responses: { '200': { description: OK } }
    post:
      summary: Queue outbound message
      parameters:
        - in: header
          name: Idempotency-Key
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/MessageCreate' }
      responses:
        '202': { description: Queued }
        '200': { description: Idempotent duplicate }
        '402': { description: Subscription/quota prevents send }
  /v1/messages/{id}:
    get:
      summary: Get message
      parameters: [ { $ref: '#/components/parameters/Id' } ]
      responses: { '200': { description: OK }, '404': { description: Not found } }
  /v2/whmcs/status:
    get:
      summary: Restricted WHMCS Sender connection/channel/usage status
      responses:
        '200': { description: OK }
        '403': { description: Token lacks WHMCS Sender permission }
  /v2/whmcs/send:
    post:
      summary: Send a WHMCS billing/service notification
      parameters:
        - in: header
          name: Idempotency-Key
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [event, to]
              properties:
                event:
                  type: string
                  enum: [invoice.created, invoice.paid, invoice.unpaid, invoice.overdue, order.created, order.paid, service.activated, service.suspended, service.unsuspended, service.terminated, custom]
                to: { type: string, description: International number or local number when country is provided }
                country: { type: string, minLength: 2, maxLength: 2, description: Optional ISO 3166-1 alpha-2 country hint for local-format numbers }
                type: { type: string, enum: [text, template, image, video, audio, document], default: text }
                message: { type: string, description: Required for text sends }
                template:
                  type: object
                  description: Required for Official Meta template sends
                  properties:
                    name: { type: string }
                    language: { type: string, default: en_US }
                    components: { type: array, items: { type: object } }
                media: { type: object, description: For media sends; supports url/link/id/data_base64, filename, mimetype and caption }
                channel_id: { type: string, nullable: true }
      responses:
        '202': { description: Queued }
        '200': { description: Idempotent duplicate }
        '402': { description: Subscription/quota prevents send }
  /v3/whmcs/status:
    get:
      summary: Restricted WHMCS Sender connection/channel/usage status
      responses:
        '200': { description: OK }
        '403': { description: Token lacks WHMCS Sender permission }
  /v3/whmcs/send:
    post:
      summary: Send a WHMCS billing/service notification
      parameters:
        - in: header
          name: Idempotency-Key
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [event, to]
              properties:
                event:
                  type: string
                  enum: [invoice.created, invoice.paid, invoice.unpaid, invoice.overdue, order.created, order.paid, service.activated, service.suspended, service.unsuspended, service.terminated, custom]
                to: { type: string, description: International number or local number when country is provided }
                country: { type: string, minLength: 2, maxLength: 2, description: Optional ISO 3166-1 alpha-2 country hint for local-format numbers }
                type: { type: string, enum: [text, template, image, video, audio, document], default: text }
                message: { type: string, description: Required for text sends }
                template:
                  type: object
                  description: Required for Official Meta template sends
                  properties:
                    name: { type: string }
                    language: { type: string, default: en_US }
                    components: { type: array, items: { type: object } }
                media: { type: object, description: For media sends; supports url/link/id/data_base64, filename, mimetype and caption }
                channel_id: { type: string, nullable: true }
      responses:
        '202': { description: Queued }
        '200': { description: Idempotent duplicate }
        '402': { description: Subscription/quota prevents send }
  /v4/whmcs/status:
    get:
      summary: Current restricted WHMCS Sender connection/channel/usage status
      responses:
        '200': { description: OK }
        '403': { description: Token lacks WHMCS Sender permission }
  /v4/whmcs/test-connection:
    post:
      summary: Validate the saved WHMCS Sender token and return channel/usage details
      responses:
        '200': { description: Connected }
        '401': { description: Invalid token }
        '403': { description: Wrong token scope or feature disabled }
  /v4/whmcs/send:
    post:
      summary: Queue a worldwide WHMCS billing/service notification
      parameters:
        - in: header
          name: Idempotency-Key
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [event, to]
              properties:
                event:
                  type: string
                  enum: [invoice.created, invoice.paid, invoice.unpaid, invoice.overdue, order.created, order.paid, service.activated, service.suspended, service.unsuspended, service.terminated, custom]
                to: { type: string, example: '08031234567', description: International number, or a national/local number when country is supplied }
                country: { type: string, example: NG, minLength: 2, maxLength: 2, description: ISO 3166-1 alpha-2 country hint. The WHMCS addon supplies this automatically from the client record. }
                type: { type: string, enum: [text, template, image, video, audio, document], default: text }
                message: { type: string }
                template: { type: object, additionalProperties: true }
                media: { type: object, description: Supports url/link/id/data_base64, filename, mimetype, caption and ptt }
                channel_id: { type: string, nullable: true }
      responses:
        '202': { description: Queued }
        '200': { description: Idempotent duplicate }
        '422': { description: Invalid phone/media/request }
        '402': { description: Subscription/quota prevents send }
  /v1/campaigns:
    get: { summary: List campaigns, responses: { '200': { description: OK } } }
    post: { summary: Create/schedule campaign, responses: { '201': { description: Created } } }
  /v1/campaigns/{id}:
    get:
      summary: Get campaign
      parameters: [ { $ref: '#/components/parameters/Id' } ]
      responses: { '200': { description: OK } }
  /v1/automations:
    get: { summary: List automation rules, responses: { '200': { description: OK } } }
    post: { summary: Create automation rule, responses: { '201': { description: Created } } }
  /v1/automations/{id}:
    patch:
      summary: Enable/disable automation
      parameters: [ { $ref: '#/components/parameters/Id' } ]
      responses: { '200': { description: OK } }
    delete:
      summary: Delete automation
      parameters: [ { $ref: '#/components/parameters/Id' } ]
      responses: { '204': { description: Deleted } }
  /v1/webhooks:
    get: { summary: List signed outbound webhooks, responses: { '200': { description: OK } } }
    post: { summary: Create signed outbound webhook, responses: { '201': { description: Created } } }
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
  parameters:
    Id:
      in: path
      name: id
      required: true
      schema: { type: string }
  schemas:
    MessageCreate:
      type: object
      required: [channel_id, to, type, payload]
      properties:
        channel_id: { type: string }
        to: { type: string, example: '+15551234567', description: International number or local number with country hint }
        country: { type: string, example: US, minLength: 2, maxLength: 2, description: Optional ISO 3166-1 alpha-2 country hint for local-format numbers }
        type:
          type: string
          enum: [text, template, image, video, audio, document, location, contacts, interactive, poll]
        payload: { type: object, additionalProperties: true }
