{
  "openapi": "3.1.0",
  "info": {
    "title": "bitbybit Messaging API",
    "description": "Send and manage WhatsApp messages through the bitbybit Open API.",
    "version": "1.0.0",
    "contact": {
      "name": "bitbybit Support",
      "url": "https://bitbybit.studio"
    }
  },
  "servers": [
    {
      "url": "https://api.bitbybit.studio/whatsapp/open/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "paths": {
    "/messages/send": {
      "post": {
        "operationId": "sendMessage",
        "summary": "Send a message",
        "description": "Send a text or image message to a WhatsApp user within the 24-hour customer service window.",
        "tags": [
          "Messages"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "to",
                  "message",
                  "sources"
                ],
                "properties": {
                  "to": {
                    "type": "string",
                    "description": "Destination phone number in WhatsApp format (e.g., 628xxxxxxxxxx)",
                    "example": "6281234567890"
                  },
                  "message": {
                    "type": "string",
                    "description": "Message content to be sent",
                    "example": "Hello! This is a sample message from WhatsApp API."
                  },
                  "imageUrl": {
                    "type": "string",
                    "description": "Image URL — if provided, sends an image message instead of text",
                    "example": "https://example.com/image.jpg"
                  },
                  "sources": {
                    "type": "string",
                    "enum": [
                      "whatsapp cloud api",
                      "whatsapp coex"
                    ],
                    "description": "Messaging channel to use"
                  },
                  "ignoreActiveTicket": {
                    "type": "boolean",
                    "default": false,
                    "description": "If true, skip sending when the chat is actively handled by an agent"
                  },
                  "countryCode": {
                    "type": "string",
                    "nullable": true,
                    "description": "Country code for number formatting (e.g., ID, EN, IN)",
                    "example": "ID"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Message sent successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "messageId2": {
                          "type": "string",
                          "description": "WhatsApp message ID"
                        },
                        "timestamp": {
                          "type": "string",
                          "description": "Message timestamp"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/messages/send-template": {
      "post": {
        "operationId": "sendTemplateMessage",
        "summary": "Send a template message",
        "description": "Send an approved WhatsApp template message. Use this to message users outside the 24-hour customer service window.",
        "tags": [
          "Messages"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "to",
                  "template_name",
                  "template_source"
                ],
                "properties": {
                  "to": {
                    "type": "string",
                    "description": "Destination phone number",
                    "example": "6281234567890"
                  },
                  "template_name": {
                    "type": "string",
                    "description": "Name of the approved WhatsApp template",
                    "example": "welcome_message"
                  },
                  "template_source": {
                    "type": "string",
                    "enum": [
                      "CRM",
                      "AUTH"
                    ],
                    "description": "Template source — CRM for business templates, AUTH for OTP"
                  },
                  "sources": {
                    "type": "string",
                    "enum": [
                      "whatsapp cloud api",
                      "whatsapp coex"
                    ],
                    "description": "Messaging channel to use"
                  },
                  "params": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Dynamic parameters to insert into the template placeholders",
                    "example": [
                      "John",
                      "Order123",
                      "2024-01-01"
                    ]
                  },
                  "mediaUrl": {
                    "type": "string",
                    "description": "Custom media URL for media templates — overrides the template default"
                  },
                  "ignoreActiveTicket": {
                    "type": "boolean",
                    "default": false,
                    "description": "If true, skip sending when the chat is actively handled by an agent"
                  },
                  "countryCode": {
                    "type": "string",
                    "nullable": true,
                    "description": "Country code for number formatting",
                    "example": "ID"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Template message sent successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "messageId2": {
                          "type": "string"
                        },
                        "timestamp": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/messages/requests": {
      "get": {
        "operationId": "listMessageRequests",
        "summary": "List API request history",
        "description": "Retrieve a paginated list of API request history with optional filtering.",
        "tags": [
          "Messages"
        ],
        "parameters": [
          {
            "name": "responseId",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by message response ID"
          },
          {
            "name": "templateId",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by template name"
          },
          {
            "name": "startDate",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Filter by start date (ISO 8601)"
          },
          {
            "name": "endDate",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Filter by end date (ISO 8601)"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 20,
              "minimum": 1,
              "maximum": 100
            },
            "description": "Number of results per page"
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            },
            "description": "Page number"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ApiRequestRecord"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PaginationMeta"
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "1598ec48-6e91-4e9a-a010-848ffb508704",
                      "companyId": 1646,
                      "messageId2": "586ff6bb-94ba-4493-93e3-9fe6a3a817c3",
                      "templateId": "order_delivered_confirmation",
                      "source": "WHATSAPP_META",
                      "request": {
                        "ip": "203.0.113.45",
                        "ips": ["203.0.113.45"],
                        "body": {
                          "to": "+62xxxxxxxxxxx",
                          "params": ["John Doe", "1790438", "Jl. Example No. 1", "John Doe", "02/07/2026 03:47"],
                          "sources": "whatsapp cloud api",
                          "mediaUrl": "https://cdn.example.com/logo.png",
                          "countryCode": "ID, EN, IN",
                          "template_name": "order_delivered_confirmation",
                          "template_source": "CRM",
                          "ignoreActiveTicket": false
                        },
                        "query": {},
                        "baseUrl": "/whatsapp/open/v1/messages",
                        "headers": {
                          "host": "api.bitbybit.studio",
                          "accept": "application/json, text/plain, */*",
                          "x-api-key": "bbb_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
                          "user-agent": "axios/1.13.2",
                          "content-type": "application/json",
                          "x-request-id": "7401fcb8-1068-45f2-a3db-8e5319304d33",
                          "content-length": "367"
                        },
                        "hostname": "api.bitbybit.studio"
                      },
                      "response": {
                        "data": {
                          "ack": 1,
                          "body": "<p>Hai Kak John Doe,<br/><br/>Pesanan 1790438 sudah tiba.</p>",
                          "type": "TEXT",
                          "media": "https://cdn.example.com/logo.png",
                          "fromMe": true,
                          "sources": "WHATSAPP_META",
                          "companyId": 1646,
                          "messageId": "wamid.HBgNNjI4OTUwMTQxMzg4NxUCABEYEjQwM0Q0RDRBMTU3NDhCNDhCQgA=",
                          "timestamp": "2026-07-31T06:00:33.405Z",
                          "clientNumber": "62xxxxxxxxxxx",
                          "messageId2": "586ff6bb-94ba-4493-93e3-9fe6a3a817c3",
                          "featureSource": "automation",
                          "connectedNumber": "908793602325790"
                        }
                      },
                      "endpointType": "send_message_template",
                      "sentAt": "2026-07-31T06:00:33.651Z",
                      "deliveredAt": "2026-07-31T06:00:35.120Z",
                      "readAt": "2026-07-31T06:01:02.014Z",
                      "repliedAt": null,
                      "failedAt": null,
                      "createdAt": "2026-07-31T06:00:33.653Z"
                    },
                    {
                      "id": "e59767c5-8d7c-4c36-b7bb-2293116cb00c",
                      "companyId": 1646,
                      "messageId2": null,
                      "templateId": "order_delivered_confirmation",
                      "source": "WHATSAPP_META",
                      "request": {
                        "ip": "203.0.113.99",
                        "ips": ["203.0.113.99"],
                        "body": {
                          "to": "+62xxxxxxxxxxx",
                          "params": ["Jane Doe", "1788668", "Jl. Example No. 2", "Jane Doe", "19/06/2026 09:41", "https://cdn.example.com/photo.jpg"],
                          "sources": "whatsapp cloud api",
                          "template_name": "order_delivered_confirmation",
                          "template_source": "CRM",
                          "ignoreActiveTicket": false
                        },
                        "query": {},
                        "baseUrl": "/whatsapp/open/v1/messages",
                        "headers": {
                          "host": "api.bitbybit.studio",
                          "x-api-key": "bbb_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
                          "user-agent": "axios/1.13.2",
                          "content-type": "application/json",
                          "x-request-id": "08b6afb9-5606-4ec1-be05-eafe6a6e5547"
                        },
                        "hostname": "api.bitbybit.studio"
                      },
                      "response": {
                        "error": {
                          "code": "SEND_FAILED",
                          "message": "Meta Error : body: number of localizable_params (6) does not match the expected number of params (5)"
                        }
                      },
                      "endpointType": "send_message_template",
                      "sentAt": null,
                      "deliveredAt": null,
                      "readAt": null,
                      "repliedAt": null,
                      "failedAt": "2026-07-31T06:00:09.120Z",
                      "createdAt": "2026-07-31T06:00:08.752Z"
                    }
                  ],
                  "meta": {
                    "total": 1192,
                    "page": 1,
                    "limit": 5,
                    "totalPages": 239,
                    "hasNextPage": true,
                    "hasPreviousPage": false
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/messages/{messageId}": {
      "get": {
        "operationId": "getMessageById",
        "summary": "Get a message",
        "description": "Retrieve a single message by its WhatsApp message ID.",
        "tags": [
          "Messages"
        ],
        "parameters": [
          {
            "name": "messageId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "WhatsApp message ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Message"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "API key for authentication. Create one in Settings > Developer."
      }
    },
    "schemas": {
      "Message": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "messageId2": {
            "type": "string",
            "description": "WhatsApp message ID"
          },
          "body": {
            "type": "string",
            "nullable": true
          },
          "type": {
            "type": "string"
          },
          "fromMe": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ApiRequestRecord": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique identifier of the API request record."
          },
          "companyId": {
            "type": "integer",
            "description": "Company the API key belongs to."
          },
          "messageId2": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Internal message ID for the resulting message. `null` when the request failed before a message was created (see `failedAt` and `response.error`)."
          },
          "templateId": {
            "type": "string",
            "nullable": true,
            "description": "Template name used, if the request was a template message."
          },
          "source": {
            "type": "string",
            "nullable": true,
            "description": "Integration source that received the request.",
            "enum": [
              "WHATSAPP_META",
              "WHATSAPP_COEX",
              "INSTAGRAM",
              "FACEBOOK",
              "TIKTOK",
              "OUTLOOK",
              "WIDGET"
            ]
          },
          "request": {
            "type": "object",
            "description": "Snapshot of the original inbound HTTP request. Fields with dynamic shape (`body`, `query`, `headers`) are returned verbatim as received; `x-api-key` in `headers` is captured — treat this payload as sensitive.",
            "properties": {
              "ip": {
                "type": "string",
                "description": "Client IP that sent the request."
              },
              "ips": {
                "type": "array",
                "items": { "type": "string" },
                "description": "Chain of forwarded client IPs (from `x-forwarded-for`)."
              },
              "body": {
                "type": "object",
                "description": "Original JSON body of the request. Shape depends on the endpoint that produced it (`endpointType`)."
              },
              "query": {
                "type": "object",
                "description": "Original query-string parameters."
              },
              "baseUrl": {
                "type": "string",
                "description": "Base path of the endpoint that was hit (e.g. `/whatsapp/open/v1/messages`)."
              },
              "headers": {
                "type": "object",
                "description": "Verbatim inbound headers, including infrastructure headers (`cf-*`, `x-forwarded-*`) and the caller's `x-api-key`."
              },
              "hostname": {
                "type": "string",
                "description": "Hostname the request was made against."
              }
            }
          },
          "response": {
            "type": "object",
            "description": "Snapshot of the response bitbybit returned to the caller. Exactly one of `data` (success) or `error` (failure) is present.",
            "properties": {
              "data": {
                "type": "object",
                "description": "Present on success. Shape depends on `endpointType` — for `send_message_template`/`send_message` this is the created message object (`ack`, `body`, `type`, `messageId`, `timestamp`, `messageId2`, `clientNumber`, `connectedNumber`, `featureSource`, and other message metadata)."
              },
              "error": {
                "type": "object",
                "description": "Present on failure.",
                "properties": {
                  "code": {
                    "type": "string",
                    "description": "Machine-readable error code (e.g. `SEND_FAILED`, `VALIDATION_ERROR`)."
                  },
                  "message": {
                    "type": "string",
                    "description": "Human-readable error message. For provider failures this includes the upstream reason (e.g. `Meta Error : …`)."
                  }
                }
              }
            }
          },
          "endpointType": {
            "type": "string",
            "description": "The bitbybit endpoint that produced this record.",
            "enum": [
              "send_message",
              "send_message_template",
              "send_message_media",
              "send_message_interactive"
            ]
          },
          "sentAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When bitbybit accepted the outbound message and dispatched it to the provider. `null` if the request failed before dispatch (see `failedAt`)."
          },
          "deliveredAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the provider confirmed delivery to the recipient device. `null` until delivery is acknowledged; remains `null` if the message never delivers."
          },
          "readAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the recipient opened / read the message. `null` if the recipient has not read it or read receipts are disabled on their side."
          },
          "repliedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the recipient sent a reply to this message. `null` if there has been no reply."
          },
          "failedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the send failed. Populated when `response.error` is set; `null` on successful sends."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When bitbybit received the inbound API call and created this record."
          }
        }
      },
      "PaginationMeta": {
        "type": "object",
        "properties": {
          "page": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "total": {
            "type": "integer"
          },
          "totalPages": {
            "type": "integer"
          },
          "hasNextPage": {
            "type": "boolean"
          },
          "hasPreviousPage": {
            "type": "boolean"
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Authentication failed",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "INVALID_API_KEY",
                "message": "Invalid or expired API key"
              }
            }
          }
        }
      },
      "Forbidden": {
        "description": "Insufficient permissions",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "INSUFFICIENT_SCOPE",
                "message": "API key does not have the required scope"
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "NOT_FOUND",
                "message": "Resource not found"
              }
            }
          }
        }
      },
      "ValidationError": {
        "description": "Validation error",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "VALIDATION_ERROR",
                "message": "Invalid request body"
              }
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limit exceeded",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "RATE_LIMIT_EXCEEDED",
                "message": "Too many requests. Please retry after the reset period."
              }
            }
          }
        }
      }
    }
  }
}