{
  "openapi": "3.1.0",
  "info": {
    "title": "Operator OTP Delivery API",
    "version": "1.0.0",
    "description": "Operator-owned WhatsApp delivery with automatic Infobip SMS fallback for approved Saudi, UAE and Polish routes. Sandbox keys are isolated from live dispatch. Each OTP is generated and verified by the operator."
  },
  "security": [
    {
      "operatorKey": []
    }
  ],
  "paths": {
    "/deliveries": {
      "post": {
        "summary": "Send an operator-generated OTP",
        "description": "Uses the operator’s saved SMS policy unless a permitted request override is supplied. Return the existing delivery for an identical retry using the same key. Conflicting reuse is rejected. Reject an already-expired request or a reference reused for a different challenge.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Send"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Delivery recorded. Channel outcomes arrive asynchronously.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Delivery"
                }
              }
            }
          },
          "409": {
            "description": "Request rejected. The response describes whether retrying is appropriate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Request rejected. The response describes whether retrying is appropriate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Request rejected. The response describes whether retrying is appropriate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "summary": "Look up deliveries by operator reference",
        "parameters": [
          {
            "name": "reference",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching tenant-scoped deliveries",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Delivery"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/deliveries/{id}": {
      "get": {
        "summary": "Read delivery status for both channels",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Latest delivery state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Delivery"
                }
              }
            }
          },
          "404": {
            "description": "Request rejected. The response describes whether retrying is appropriate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/deliveries/{id}/cancel-fallback": {
      "post": {
        "summary": "Suppress pending SMS when the operator invalidates or verifies a code",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Fallback cancelled or already suppressed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Delivery"
                }
              }
            }
          },
          "409": {
            "description": "SMS submission has already begun",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/senders": {
      "get": {
        "summary": "List authorized WhatsApp senders and their ready template languages",
        "responses": {
          "200": {
            "description": "Operator senders",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Sender"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/reports/delivery": {
      "get": {
        "summary": "Usage, failures and delivery times for this operator",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "description": "UTC timestamp with a time zone",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "description": "UTC timestamp with a time zone",
              "format": "date-time"
            }
          },
          {
            "name": "sender_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Date-filtered delivery totals. A delivery received on both channels counts once in delivered_by_either_channel.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Report"
                }
              }
            }
          }
        }
      }
    },
    "/sandbox/deliveries/{id}/simulate": {
      "post": {
        "summary": "Simulate an outcome using a test API key",
        "description": "Test credentials operate only on isolated simulated deliveries. Neither channel sends live messages.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "scenario": {
                    "type": "string",
                    "enum": [
                      "whatsapp_delivered",
                      "whatsapp_read",
                      "whatsapp_failed",
                      "whatsapp_timeout",
                      "late_whatsapp_delivery",
                      "sms_delivered",
                      "sms_failed",
                      "duplicate_receipt"
                    ]
                  }
                },
                "required": [
                  "scenario"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Simulated delivery",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Delivery"
                }
              }
            }
          },
          "403": {
            "description": "Request rejected. The response describes whether retrying is appropriate.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "deliveryUpdated": {
      "post": {
        "summary": "Signed event sent to the operator’s configured HTTPS callback",
        "description": "At-least-once delivery. Verify the timestamp and HMAC-SHA256 over timestamp + \".\" + raw request body before processing. Deduplicate by event_id. Retry callbacks independently of message sends.",
        "parameters": [
          {
            "name": "X-Delivery-Timestamp",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "X-Delivery-Signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "event_id": {
                    "type": "string",
                    "description": "Unique event identifier"
                  },
                  "occurred_at": {
                    "type": "string",
                    "description": "UTC timestamp with a time zone",
                    "format": "date-time"
                  },
                  "delivery": {
                    "$ref": "#/components/schemas/Delivery"
                  }
                },
                "required": [
                  "event_id",
                  "occurred_at",
                  "delivery"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Acknowledged"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "operatorKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Tenant-scoped live or sandbox API key"
      }
    },
    "schemas": {
      "Fallback": {
        "type": "object",
        "properties": {
          "mode": {
            "type": "string",
            "enum": [
              "disabled",
              "on_failure",
              "on_failure_or_timeout"
            ]
          },
          "wait_seconds": {
            "type": "integer",
            "minimum": 5,
            "description": "Elapsed time after WhatsApp submission before fallback is eligible when no delivery receipt has arrived. Required for on_failure_or_timeout.",
            "maximum": 600
          }
        },
        "required": [
          "mode"
        ],
        "additionalProperties": false,
        "allOf": [
          {
            "if": {
              "properties": {
                "mode": {
                  "const": "on_failure_or_timeout"
                }
              }
            },
            "then": {
              "required": [
                "wait_seconds"
              ]
            }
          }
        ]
      },
      "Send": {
        "type": "object",
        "properties": {
          "reference": {
            "type": "string",
            "description": "Unique operator reference for this delivery. A newly issued OTP must have a new reference."
          },
          "to": {
            "type": "string",
            "description": "Recipient phone number in international format",
            "pattern": "^\\+[1-9][0-9]{6,14}$"
          },
          "code": {
            "type": "string",
            "description": "Operator-generated OTP. Preserve leading zeros. Never included in responses, reports, webhooks or logs.",
            "minLength": 1,
            "writeOnly": true,
            "pattern": "^[0-9]{4,10}$"
          },
          "expires_at": {
            "type": "string",
            "description": "ISO timestamp with timezone, more than two seconds and at most one hour from acceptance.",
            "format": "date-time"
          },
          "sender_id": {
            "type": "string",
            "description": "One of this operator’s authorized WhatsApp senders"
          },
          "language": {
            "type": "string",
            "description": "Language of the approved authentication template"
          },
          "sms_fallback": {
            "$ref": "#/components/schemas/Fallback"
          }
        },
        "required": [
          "reference",
          "to",
          "code",
          "expires_at",
          "sender_id",
          "language"
        ],
        "additionalProperties": false
      },
      "Attempt": {
        "type": "object",
        "properties": {
          "channel": {
            "type": "string",
            "enum": [
              "whatsapp",
              "sms"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "accepted",
              "sent",
              "delivered",
              "read",
              "failed",
              "unknown",
              "expired",
              "cancelled",
              "submitting"
            ]
          },
          "trigger": {
            "type": "string",
            "enum": [
              "initial",
              "whatsapp_failure",
              "whatsapp_delivery_unconfirmed"
            ]
          },
          "updated_at": {
            "type": "string",
            "description": "UTC timestamp with a time zone",
            "format": "date-time"
          },
          "failure_code": {
            "type": "string",
            "description": "Normalized failure category when available"
          },
          "failure_detail": {
            "type": "string",
            "description": "Explanation safe to display to the operator"
          }
        },
        "required": [
          "channel",
          "status",
          "trigger",
          "updated_at"
        ],
        "additionalProperties": false
      },
      "Delivery": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Opaque delivery ID"
          },
          "reference": {
            "type": "string",
            "description": "Operator reference"
          },
          "attempts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Attempt"
            }
          },
          "expires_at": {
            "type": "string",
            "description": "UTC timestamp with a time zone",
            "format": "date-time"
          },
          "fallback_status": {
            "type": "string",
            "enum": [
              "disabled",
              "waiting",
              "claimed",
              "submitted",
              "suppressed",
              "cancelled",
              "expired"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "mode": {
            "type": "string",
            "enum": [
              "test",
              "live"
            ]
          },
          "sender_id": {
            "type": "string"
          },
          "recipient": {
            "type": "string",
            "description": "Masked number"
          },
          "fallback_reason": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "reference",
          "attempts",
          "expires_at",
          "fallback_status"
        ],
        "additionalProperties": false
      },
      "Error": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "description": "Stable error code"
          },
          "message": {
            "type": "string",
            "description": "Explanation"
          },
          "retryable": {
            "type": "boolean"
          }
        },
        "required": [
          "code",
          "message",
          "retryable"
        ],
        "additionalProperties": false
      },
      "Sender": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Tenant-scoped sender ID"
          },
          "languages": {
            "type": "array",
            "items": {
              "type": "string",
              "description": "Approved template language"
            }
          },
          "status": {
            "type": "string",
            "enum": [
              "ready",
              "action_required"
            ]
          },
          "title": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "languages",
          "status"
        ],
        "additionalProperties": false
      },
      "Report": {
        "type": "object",
        "properties": {
          "requested": {
            "type": "integer",
            "minimum": 0
          },
          "whatsapp_delivered": {
            "type": "integer",
            "minimum": 0
          },
          "sms_triggered": {
            "type": "integer",
            "minimum": 0
          },
          "sms_delivered": {
            "type": "integer",
            "minimum": 0
          },
          "delivered_by_either_channel": {
            "type": "integer",
            "minimum": 0
          },
          "delivery_unconfirmed": {
            "type": "integer",
            "minimum": 0
          },
          "median_delivery_seconds": {
            "type": [
              "number",
              "null"
            ]
          },
          "failures_by_reason": {
            "type": "object",
            "additionalProperties": {
              "type": "integer",
              "minimum": 0
            }
          },
          "alert": {
            "type": [
              "object",
              "null"
            ],
            "description": "Elevated recent failure alert for this operator and environment"
          }
        },
        "required": [
          "requested",
          "sms_triggered",
          "delivered_by_either_channel"
        ],
        "additionalProperties": false
      }
    }
  },
  "x-fallback-rules": [
    "Require an approved sender per operator and destination, available SMS quota and a permitted fallback policy. Suppress fallback on blocked or policy-restricted WhatsApp outcomes.",
    "On an eligible definitive WhatsApp failure, schedule one SMS immediately. On timeout, record delivery as unconfirmed. Delivered or read suppresses a pending SMS. Lack of a read receipt alone never triggers SMS.",
    "Use a durable timer and an atomic one-time claim per operator delivery. Recheck the latest receipt, cancellation, and remaining OTP lifetime just before provider submission. Ignore duplicate and out-of-order callbacks. Never blindly resubmit after ambiguous SMS provider acceptance.",
    "Use the same code and recipient for both channels. Encrypt any temporarily retained code, redact provider payloads from logs, and purge the code when no dispatch can still use it or at expiry.",
    "A late WhatsApp receipt can arrive after SMS submission. Record both outcomes against the same delivery. An in-flight carrier message cannot be recalled.",
    "Verify provider callbacks before applying status. Enforce daily request limits and monthly SMS attempt caps. Report an alert when recent failures rise."
  ],
  "servers": [
    {
      "url": "https://ebeeyupmzabiqrzsebkm.supabase.co/functions/v1/relay"
    }
  ]
}
