{
  "openapi": "3.0.3",
  "info": {
    "title": "WhatsOS OTP: verification and supplied-code delivery",
    "version": "1.2.0",
    "description": "Choose per request: WhatsOS generates and verifies via /verifications, or your software generates and verifies while WhatsOS delivers via /deliveries. Both reuse existing project API keys. Test projects simulate delivery and never send WhatsApp messages."
  },
  "servers": [
    {
      "url": "https://whatsos.com/api/v1/otp",
      "description": "WhatsOS production API; use test keys for simulated delivery"
    }
  ],
  "security": [
    {
      "ApiKey": []
    }
  ],
  "paths": {
    "/verifications": {
      "post": {
        "summary": "Request a verification code",
        "operationId": "sendVerification",
        "description": "Requires otp:send. All access is scoped to the API key project/environment.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9_.:-]+$"
            },
            "description": "Retain for retries of the same operation. Results are retained for 24 hours."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Verification"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "403": {
            "description": "Not permitted or quota exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "409": {
            "description": "State/idempotency conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "413": {
            "description": "Payload too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "429": {
            "description": "Rate limit or cooldown",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Suggested delay in seconds"
              }
            }
          },
          "503": {
            "description": "Temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          }
        }
      }
    },
    "/verifications/{id}": {
      "get": {
        "summary": "Read a verification",
        "operationId": "getVerification",
        "description": "Requires otp:read.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^ver_[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Masked status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Verification"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "403": {
            "description": "Not permitted or quota exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "409": {
            "description": "State/idempotency conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "413": {
            "description": "Payload too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "429": {
            "description": "Rate limit or cooldown",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Suggested delay in seconds"
              }
            }
          },
          "503": {
            "description": "Temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          }
        }
      }
    },
    "/verifications/{id}/check": {
      "post": {
        "summary": "Check a code",
        "operationId": "checkVerification",
        "description": "Requires otp:verify. All access is scoped to the API key project/environment.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^ver_[A-Za-z0-9_-]+$"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9_.:-]+$"
            },
            "description": "Retain for retries of the same operation. Results are retained for 24 hours."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CheckRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Outcome",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Verification"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "403": {
            "description": "Not permitted or quota exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "409": {
            "description": "State/idempotency conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "413": {
            "description": "Payload too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "429": {
            "description": "Rate limit or cooldown",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Suggested delay in seconds"
              }
            }
          },
          "503": {
            "description": "Temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          }
        }
      }
    },
    "/verifications/{id}/resend": {
      "post": {
        "summary": "Replace a challenge after cooldown",
        "operationId": "resendVerification",
        "description": "Requires otp:send. All access is scoped to the API key project/environment.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^ver_[A-Za-z0-9_-]+$"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9_.:-]+$"
            },
            "description": "Retain for retries of the same operation. Results are retained for 24 hours."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmptyRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Verification"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "403": {
            "description": "Not permitted or quota exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "409": {
            "description": "State/idempotency conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "413": {
            "description": "Payload too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "429": {
            "description": "Rate limit or cooldown",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Suggested delay in seconds"
              }
            }
          },
          "503": {
            "description": "Temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          }
        }
      }
    },
    "/verifications/{id}/cancel": {
      "post": {
        "summary": "Cancel an active challenge",
        "operationId": "cancelVerification",
        "description": "Requires otp:cancel. All access is scoped to the API key project/environment.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^ver_[A-Za-z0-9_-]+$"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9_.:-]+$"
            },
            "description": "Retain for retries of the same operation. Results are retained for 24 hours."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmptyRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Outcome",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Verification"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "403": {
            "description": "Not permitted or quota exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "409": {
            "description": "State/idempotency conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "413": {
            "description": "Payload too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "429": {
            "description": "Rate limit or cooldown",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Suggested delay in seconds"
              }
            }
          },
          "503": {
            "description": "Temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          }
        }
      }
    },
    "/deliveries": {
      "post": {
        "summary": "Deliver a code generated by your software",
        "operationId": "deliverOtp",
        "description": "Requires otp:send. No WhatsOS verification challenge is created. Your backend owns code generation, verification, validity and business actions. Subscription, consent, sender readiness, billing and shared OTP rate limits apply.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 128,
              "pattern": "^[A-Za-z0-9_.:-]+$"
            },
            "description": "Retain for retries of the same operation. Results are retained for 24 hours."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeliveryRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued for delivery; not proof of message delivery or verification. An exact idempotent replay returns this original outcome.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Delivery"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "403": {
            "description": "Not permitted or quota exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "409": {
            "description": "State/idempotency conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "413": {
            "description": "Payload too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "429": {
            "description": "Rate limit or cooldown",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Suggested delay in seconds"
              }
            }
          },
          "503": {
            "description": "Temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          }
        }
      }
    },
    "/deliveries/{id}": {
      "get": {
        "summary": "Read supplied-code delivery status",
        "operationId": "getOtpDelivery",
        "description": "Requires otp:read. Project-scoped masked delivery status. There is no verification/check/cancel/resend operation for these IDs.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^odl_[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Latest delivery status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Delivery"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "403": {
            "description": "Not permitted or quota exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "409": {
            "description": "State/idempotency conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "413": {
            "description": "Payload too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          },
          "429": {
            "description": "Rate limit or cooldown",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Suggested delay in seconds"
              }
            }
          },
          "503": {
            "description": "Temporarily unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {}
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "A scoped wotp_test_ or wotp_live_ API secret. Never expose in browser or mobile code."
      }
    },
    "schemas": {
      "SendRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "to",
          "purpose"
        ],
        "properties": {
          "to": {
            "type": "string",
            "pattern": "^\\+[1-9]\\d{7,14}$",
            "example": "+919876543210"
          },
          "purpose": {
            "type": "string",
            "pattern": "^[a-z_]{2,40}$",
            "example": "login"
          },
          "reference": {
            "type": "string",
            "maxLength": 120,
            "default": "",
            "description": "Opaque backend action-session reference."
          },
          "locale": {
            "type": "string",
            "maxLength": 20,
            "default": "en_US"
          }
        }
      },
      "CheckRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "code"
        ],
        "properties": {
          "code": {
            "type": "string",
            "pattern": "^\\d{6}$",
            "example": "123456"
          }
        }
      },
      "EmptyRequest": {
        "type": "object",
        "additionalProperties": false
      },
      "Verification": {
        "type": "object",
        "required": [
          "verification_id",
          "status",
          "delivery_status"
        ],
        "properties": {
          "verification_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "verified",
              "expired",
              "locked",
              "cancelled",
              "superseded",
              "failed"
            ]
          },
          "delivery_status": {
            "type": "string",
            "enum": [
              "queued",
              "submitting",
              "accepted",
              "sent",
              "delivered",
              "read",
              "failed",
              "unknown"
            ]
          },
          "verified": {
            "type": "boolean",
            "description": "Present on a check outcome. A replay may return the original true outcome without consuming again."
          },
          "to": {
            "type": "string",
            "description": "Masked recipient"
          },
          "purpose": {
            "type": "string"
          },
          "reference": {
            "type": "string"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "expires_in": {
            "type": "integer",
            "minimum": 0
          },
          "resend_after": {
            "type": "integer",
            "minimum": 0
          },
          "verified_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "error_code": {
            "type": "string",
            "nullable": true
          },
          "request_id": {
            "type": "string"
          },
          "provider_error": {
            "$ref": "#/components/schemas/ProviderDiagnostic"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          },
          "request_id": {
            "type": "string"
          }
        }
      },
      "CustomerEvent": {
        "type": "object",
        "required": [
          "id",
          "type",
          "schema_version",
          "project_id",
          "environment",
          "verification_id",
          "occurred_at"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "verification.verified",
              "verification.expired",
              "verification.locked",
              "message.delivered",
              "message.failed"
            ]
          },
          "schema_version": {
            "type": "string",
            "enum": [
              "1"
            ]
          },
          "project_id": {
            "type": "string"
          },
          "environment": {
            "type": "string",
            "enum": [
              "test",
              "live"
            ]
          },
          "verification_id": {
            "type": "string"
          },
          "reference": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "delivery_status": {
            "type": "string"
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time"
          },
          "provider_error": {
            "$ref": "#/components/schemas/ProviderDiagnostic"
          },
          "error_code": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "DeliveryRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "to",
          "code",
          "expires_at"
        ],
        "properties": {
          "to": {
            "type": "string",
            "pattern": "^\\+[1-9]\\d{7,14}$",
            "example": "+971501234567"
          },
          "code": {
            "type": "string",
            "pattern": "^\\d{6}$",
            "writeOnly": true,
            "example": "004281",
            "description": "Exactly six digits as a string; leading zeros are preserved. Never returned or logged by WhatsOS."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "Absolute expiry in ISO 8601 with timezone, 30 seconds to five minutes ahead at first submission. Retain this exact timestamp on retry. Queued submission stops when less than 30 seconds remain; accepted WhatsApp messages cannot be recalled. Your software enforces actual code validity. Match your template expiry wording."
          },
          "purpose": {
            "type": "string",
            "pattern": "^[a-z_]{2,40}$",
            "default": "login",
            "description": "Must be enabled on the project."
          },
          "reference": {
            "type": "string",
            "maxLength": 120,
            "default": "",
            "description": "Opaque business reference, not a duplicate-send key. Keep secrets and OTP codes out of references."
          },
          "locale": {
            "type": "string",
            "pattern": "^[a-z]{2}(?:_[A-Z]{2})?$",
            "description": "Optional. Defaults to the project template language; if provided, must match it."
          }
        }
      },
      "Delivery": {
        "type": "object",
        "required": [
          "delivery_id",
          "mode",
          "status",
          "to",
          "expires_at"
        ],
        "properties": {
          "delivery_id": {
            "type": "string",
            "pattern": "^odl_"
          },
          "mode": {
            "type": "string",
            "enum": [
              "delivery_only"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "submitting",
              "accepted",
              "sent",
              "delivered",
              "read",
              "failed",
              "unknown",
              "expired"
            ]
          },
          "to": {
            "type": "string",
            "description": "Masked recipient"
          },
          "purpose": {
            "type": "string"
          },
          "reference": {
            "type": "string"
          },
          "template": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "nullable": true
              },
              "language": {
                "type": "string"
              }
            }
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "error_code": {
            "type": "string",
            "nullable": true
          },
          "request_id": {
            "type": "string"
          },
          "provider_error": {
            "$ref": "#/components/schemas/ProviderDiagnostic"
          }
        }
      },
      "DeliveryEvent": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Delivery"
          },
          {
            "type": "object",
            "required": [
              "id",
              "type",
              "schema_version",
              "project_id",
              "environment",
              "occurred_at"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "type": {
                "type": "string",
                "enum": [
                  "otp.delivery.accepted",
                  "otp.delivery.sent",
                  "otp.delivery.delivered",
                  "otp.delivery.read",
                  "otp.delivery.failed",
                  "otp.delivery.unknown",
                  "otp.delivery.expired"
                ]
              },
              "schema_version": {
                "type": "string",
                "enum": [
                  "1"
                ]
              },
              "project_id": {
                "type": "string"
              },
              "environment": {
                "type": "string",
                "enum": [
                  "test",
                  "live"
                ]
              },
              "occurred_at": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        ]
      },
      "ProviderDiagnostic": {
        "type": "object",
        "nullable": true,
        "additionalProperties": false,
        "required": [
          "stage"
        ],
        "description": "Safe diagnostic metadata for new provider failures. Null when unavailable; historical failure details cannot be recovered. Raw provider text, credentials, recipient data and OTP values are not retained here.",
        "properties": {
          "stage": {
            "type": "string",
            "enum": [
              "template_check",
              "submission",
              "delivery"
            ]
          },
          "code": {
            "type": "integer",
            "minimum": 0,
            "maximum": 2147483647
          },
          "subcode": {
            "type": "integer",
            "minimum": 0,
            "maximum": 2147483647
          },
          "http_status": {
            "type": "integer",
            "minimum": 400,
            "maximum": 599
          }
        }
      }
    }
  }
}
