{
  "openapi": "3.0.3",
  "info": {
    "title": "WhatsOS Bills & Receipts API",
    "version": "1.2.0",
    "description": "Automatic POS invoice and paid-receipt delivery. Configure once in OTP → Bills & Receipts, validate the POS request, then enable new-transaction automation. Existing invoice and message endpoints remain compatible."
  },
  "servers": [
    {
      "url": "https://whatsos.com"
    }
  ],
  "security": [
    {
      "receiptKey": []
    }
  ],
  "paths": {
    "/api/v1/messaging/connection": {
      "get": {
        "summary": "Check key and sending readiness without sending",
        "responses": {
          "200": {
            "description": "Authenticated project, permissions, environment and readiness"
          },
          "401": {
            "description": "Invalid or expired API key"
          }
        }
      }
    },
    "/api/v1/messaging/messages": {
      "post": {
        "summary": "Queue a bill, invoice or receipt",
        "parameters": [
          {
            "in": "header",
            "name": "Idempotency-Key",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Receipt"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued or replay of the same operation; includes message_id and status"
          },
          "400": {
            "description": "Invalid receipt, consent or template variables"
          },
          "403": {
            "description": "Scope, readiness, entitlement or customer opt-out restriction"
          },
          "409": {
            "description": "Template or duplicate-content conflict"
          },
          "429": {
            "description": "Rate limit"
          }
        }
      }
    },
    "/api/v1/messaging/messages/{message_id}": {
      "get": {
        "summary": "Read delivery status",
        "parameters": [
          {
            "in": "path",
            "name": "message_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Message status for the authenticated project"
          },
          "404": {
            "description": "Message unavailable in this project"
          }
        }
      }
    },
    "/api/v1/messaging/integrations/{integration_id}/events": {
      "post": {
        "summary": "Submit a sale to a configured API integration",
        "parameters": [
          {
            "in": "path",
            "name": "integration_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "event_id",
                  "data"
                ],
                "additionalProperties": false,
                "properties": {
                  "event_id": {
                    "type": "string",
                    "maxLength": 160
                  },
                  "data": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Event stored durably; includes event_id, status and message_id when available"
          },
          "409": {
            "description": "Paused or changed integration, or event content conflict"
          }
        }
      }
    },
    "/api/v1/messaging/documents": {
      "post": {
        "summary": "Upload a PDF up to 5 MB; private download link expires after 48 hours",
        "parameters": [
          {
            "in": "header",
            "name": "X-Filename",
            "schema": {
              "type": "string",
              "default": "receipt.pdf"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/pdf": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "document_id, url, filename and expires_at"
          },
          "400": {
            "description": "Invalid PDF"
          },
          "413": {
            "description": "File too large"
          },
          "429": {
            "description": "Temporary storage or request limit"
          }
        }
      }
    },
    "/api/v1/messaging/invoices": {
      "post": {
        "summary": "Send an invoice view/download link using the saved project template",
        "description": "Choose an approved one-variable text utility template once in Bills & Receipts → Templates. Requires messages:send. Sandbox delivery is simulated.",
        "parameters": [
          {
            "in": "header",
            "name": "Idempotency-Key",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoiceLink"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued or replay of the same operation; includes message_id and status"
          },
          "400": {
            "description": "Invalid receipt, consent or template variables"
          },
          "403": {
            "description": "Scope, readiness, entitlement or customer opt-out restriction"
          },
          "409": {
            "description": "Template or duplicate-content conflict"
          },
          "429": {
            "description": "Rate limit"
          }
        }
      }
    },
    "/api/v1/messaging/billing-events": {
      "post": {
        "summary": "Automatically deliver a new paid receipt or issued invoice",
        "description": "Requires messages:send and tested, enabled automation. Durable intake returns event_id. Duplicate document IDs reuse the event; changed customer/payment data returns 409. Only a missing document URL can be added later. Store this call in the POS transactional outbox after the payment commits. Retrying must keep the original timestamp and data.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BillingEvent"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted: pending, waiting_document, or skipped for transactions before activation. Use GET status to confirm actual delivery."
          },
          "400": {
            "description": "Invalid event, unpaid status, missing payment ID/consent, future timestamp, or unsafe URL"
          },
          "401": {
            "description": "Invalid/expired API key"
          },
          "403": {
            "description": "Missing permission or suspended project"
          },
          "404": {
            "description": "Configure this event in Bills & Receipts first"
          },
          "409": {
            "description": "Paused/unverified automation or document identity conflict"
          },
          "429": {
            "description": "Queue/rate limit; retry original event with backoff"
          },
          "500": {
            "description": "Temporary failure; retry original event, never invent a new document ID"
          }
        }
      }
    },
    "/api/v1/messaging/billing-events/validate": {
      "post": {
        "summary": "Validate the POS sample without sending",
        "description": "Requires messages:send. Supply document_url. Checks project key, sender, saved template and complete sample; enables no sending. After validation, an administrator can enable automation.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BillingEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Valid sample; no message sent"
          },
          "400": {
            "description": "Invalid sample"
          },
          "401": {
            "description": "Invalid key"
          },
          "403": {
            "description": "Missing scope"
          },
          "409": {
            "description": "Template or configuration needs attention"
          }
        }
      }
    },
    "/api/v1/messaging/billing-events/{event_id}": {
      "get": {
        "summary": "Read project-scoped event and WhatsApp delivery status",
        "description": "Requires messages:read. Includes waiting_document, skipped, pending, queued, accepted, sent, delivered, read, failed, cancelled or unknown. Do not resend an unknown delivery.",
        "parameters": [
          {
            "in": "path",
            "name": "event_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Current intake/delivery status, message_id when queued, masked recipient metadata, error_code and expiry"
          },
          "401": {
            "description": "Invalid key"
          },
          "403": {
            "description": "Missing scope"
          },
          "404": {
            "description": "Event not found in this project"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "receiptKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Project API key with messages:send and/or messages:read. Existing wotp-prefixed keys remain compatible."
      }
    },
    "schemas": {
      "Receipt": {
        "type": "object",
        "required": [
          "to",
          "kind",
          "reference",
          "template",
          "consent"
        ],
        "additionalProperties": false,
        "properties": {
          "to": {
            "type": "string",
            "pattern": "^\\+[1-9]\\d{7,14}$"
          },
          "kind": {
            "type": "string",
            "enum": [
              "invoice",
              "receipt",
              "bill"
            ]
          },
          "reference": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "Tracking and duplicate prevention only; never a substitute for the PDF."
          },
          "template": {
            "type": "object",
            "required": [
              "name",
              "language"
            ],
            "properties": {
              "name": {
                "type": "string"
              },
              "language": {
                "type": "string"
              },
              "parameters": {
                "type": "array",
                "maxItems": 20,
                "items": {
                  "type": "string",
                  "maxLength": 1024
                }
              }
            }
          },
          "document": {
            "type": "object",
            "required": [
              "url"
            ],
            "properties": {
              "url": {
                "type": "string",
                "format": "uri"
              },
              "filename": {
                "type": "string",
                "default": "invoice.pdf"
              }
            },
            "description": "Optional PDF attachment; requires a PDF-header utility template. For link delivery pass the HTTPS URL in a body variable, or use the simpler /invoices endpoint."
          },
          "consent": {
            "type": "object",
            "required": [
              "obtained",
              "source"
            ],
            "properties": {
              "obtained": {
                "type": "boolean",
                "enum": [
                  true
                ]
              },
              "source": {
                "type": "string",
                "minLength": 3,
                "maxLength": 120
              }
            }
          }
        }
      },
      "InvoiceLink": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "to",
          "invoice_url",
          "consent"
        ],
        "properties": {
          "to": {
            "type": "string",
            "pattern": "^\\+[1-9]\\d{7,14}$"
          },
          "invoice_url": {
            "type": "string",
            "format": "uri",
            "maxLength": 1024,
            "description": "Public HTTPS invoice view/download URL hosted by the client. Do not require browser login."
          },
          "reference": {
            "type": "string",
            "maxLength": 120,
            "description": "Invoice identifier. Defaults to a stable identifier derived from the Idempotency-Key."
          },
          "consent": {
            "type": "object",
            "required": [
              "obtained",
              "source"
            ],
            "properties": {
              "obtained": {
                "type": "boolean",
                "enum": [
                  true
                ]
              },
              "source": {
                "type": "string",
                "minLength": 3,
                "maxLength": 120
              }
            }
          }
        }
      },
      "BillingEvent": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "event",
          "document_id",
          "status",
          "occurred_at",
          "to",
          "amount",
          "currency",
          "consent"
        ],
        "properties": {
          "event": {
            "type": "string",
            "enum": [
              "receipt.paid",
              "invoice.issued"
            ]
          },
          "document_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80,
            "description": "Stable unique document ID within this project and event type; reuse with identical content on retries."
          },
          "payment_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80,
            "description": "Required for receipt.paid. The POS must supply its confirmed payment ID."
          },
          "status": {
            "type": "string",
            "enum": [
              "paid",
              "issued"
            ],
            "description": "receipt.paid requires paid; invoice.issued requires issued."
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time",
            "description": "Authoritative payment/issue timestamp, not the retry time. Transactions before first activation are skipped; timestamps over one minute in the future are rejected."
          },
          "to": {
            "type": "string",
            "pattern": "^\\+[1-9]\\d{7,14}$"
          },
          "customer_name": {
            "type": "string",
            "maxLength": 100,
            "default": "Customer"
          },
          "amount": {
            "type": "string",
            "pattern": "^\\d{1,12}(?:\\.\\d{1,4})?$"
          },
          "currency": {
            "type": "string",
            "pattern": "^[A-Z]{3}$"
          },
          "document_url": {
            "type": "string",
            "format": "uri",
            "maxLength": 1024,
            "description": "HTTPS link to the customer-accessible document. Omit while generating it; POST identical fields plus this URL within seven days. It cannot be replaced after submission."
          },
          "consent": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "obtained",
              "source"
            ],
            "properties": {
              "obtained": {
                "type": "boolean",
                "enum": [
                  true
                ]
              },
              "source": {
                "type": "string",
                "minLength": 3,
                "maxLength": 120
              }
            }
          }
        },
        "oneOf": [
          {
            "properties": {
              "event": {
                "enum": [
                  "receipt.paid"
                ]
              },
              "status": {
                "enum": [
                  "paid"
                ]
              }
            },
            "required": [
              "payment_id"
            ]
          },
          {
            "properties": {
              "event": {
                "enum": [
                  "invoice.issued"
                ]
              },
              "status": {
                "enum": [
                  "issued"
                ]
              }
            }
          }
        ],
        "example": {
          "event": "receipt.paid",
          "document_id": "RCPT-1042",
          "payment_id": "PAY-1042",
          "status": "paid",
          "occurred_at": "2026-10-22T12:00:00Z",
          "to": "+919000000001",
          "customer_name": "Customer name",
          "amount": "250.00",
          "currency": "AED",
          "document_url": "https://your-pos.example/receipts/1042.pdf",
          "consent": {
            "obtained": true,
            "source": "Customer requested WhatsApp receipt at checkout"
          }
        }
      }
    }
  }
}
