{
  "openapi": "3.1.0",
  "info": {
    "title": "Meser 10 Verify API",
    "version": "7.2.0",
    "description": "Phone verification by SMS in two calls: /v1/send creates, sends and stores the code; /v1/check checks what the user typed. Server-to-server only: the ApiKey is a full account key and must never reach a browser or an app. Guide: https://www.meser10.co.il/en/support/verify/",
    "contact": {
      "name": "Meser 10 support",
      "email": "support@meser10.co.il",
      "url": "https://www.meser10.co.il/en/support/verify/"
    }
  },
  "externalDocs": {
    "description": "Reference: fields, answers and errors",
    "url": "https://www.meser10.co.il/en/verify/#reference"
  },
  "servers": [
    {
      "url": "https://verify.mesereser.com"
    }
  ],
  "security": [
    {
      "ApiKey": [],
      "VerifyToken": []
    }
  ],
  "paths": {
    "/v1/send": {
      "post": {
        "operationId": "sendCode",
        "summary": "Send a verification code by SMS",
        "description": "Limits per number: 30 seconds between sends, 3 per 10 minutes, 10 per 24 hours; per account: 2,000 per day. The SMS is charged in your Meser 10 account. With a test token nothing is sent and the code is returned in test_code.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "phone",
                  "from"
                ],
                "additionalProperties": true,
                "properties": {
                  "phone": {
                    "type": "string",
                    "description": "Israeli mobile number (050-055, 058): 05XXXXXXXX, or 9725XXXXXXXX / +9725XXXXXXXX.",
                    "example": "0501234567"
                  },
                  "from": {
                    "type": "string",
                    "description": "A sender name verified in your Meser 10 account (2-11 Latin letters, digits or spaces) or a verified sender number (0 + 8-9 digits).",
                    "example": "MyShop"
                  },
                  "lang": {
                    "type": "string",
                    "enum": [
                      "he",
                      "en"
                    ],
                    "default": "he",
                    "description": "Language of the default message text."
                  },
                  "template": {
                    "type": "string",
                    "description": "Your own message text. Must contain {code} exactly once, no links, no phone numbers, no card/password/bank words, and fit one SMS (70 characters with Hebrew, 160 without).",
                    "example": "{code} is your MyShop code"
                  },
                  "ttl": {
                    "type": "integer",
                    "minimum": 60,
                    "maximum": 900,
                    "default": 300,
                    "description": "Seconds the code stays valid."
                  },
                  "autofill": {
                    "type": "string",
                    "enum": [
                      "web",
                      "android"
                    ],
                    "description": "Adds an autofill line to the SMS. web: the last line becomes '@<your domain> #<code>' (WebOTP, iOS and Android browsers). android: appends your app hash (Android SMS Retriever). The domain and the app hash are set on your token by Meser 10 support; true means web."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Code sent.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "id": {
                      "type": "string",
                      "description": "Request id. Keep it with the user's session and send it to /v1/check."
                    },
                    "expires_in": {
                      "type": "integer",
                      "example": 300
                    },
                    "phone": {
                      "type": "string",
                      "description": "Masked number.",
                      "example": "050*****67"
                    },
                    "test": {
                      "type": "boolean",
                      "description": "Present and true only with a test token: no SMS was sent."
                    },
                    "test_code": {
                      "type": "string",
                      "description": "Test token only: the code that would have been sent."
                    },
                    "message": {
                      "type": "string",
                      "description": "Test token only: the exact SMS text that would have been sent."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid input, or the send was refused (error: invalid_phone, invalid_from, invalid_template, template_needs_one_code, template_links_not_allowed, template_not_allowed, template_too_long, send_failed, invalid_api_key, invalid_json, invalid_autofill, autofill_not_configured).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key, auth_failed, or key_refused: the ApiKey was refused, so sends of this token pause for 15 minutes (doubling up to 24 h) and resume by themselves; checks keep working.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "not_enabled (X-Verify-Token missing, unknown or disabled) or forbidden.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "too_soon (with retry_after), phone_rate_limited, account_daily_limit, token_daily_limit, too_many_requests or too_many_failures.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "upstream_uncertain (the SMS may have been sent; id is returned and can still be checked), sender_not_verified, no_credits, rejected or upstream_blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "temporarily_unavailable (with retry_after).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/check": {
      "post": {
        "operationId": "checkCode",
        "summary": "Check the code the user typed",
        "description": "Use the same ApiKey and token as the send. Five wrong attempts block the request; the fifth wrong answer returns wrong_code with attempts_left 0.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "id",
                  "code",
                  "phone"
                ],
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "The id returned by /v1/send."
                  },
                  "code": {
                    "type": "string",
                    "pattern": "^[0-9]{6,8}$",
                    "description": "The code the user typed, as a string.",
                    "example": "481902"
                  },
                  "phone": {
                    "type": "string",
                    "description": "Required. The number the code was sent to; a different number counts as a wrong attempt."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "ok:true + verified:true, or ok:false + verified:false with error/reason.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "true only when the code was verified; false for every not-verified answer."
                    },
                    "verified": {
                      "type": "boolean"
                    },
                    "error": {
                      "type": "string",
                      "description": "Why verified is false (same value as reason)."
                    },
                    "phone": {
                      "type": "string",
                      "description": "Masked number, when verified."
                    },
                    "reason": {
                      "type": "string",
                      "enum": [
                        "wrong_code",
                        "expired",
                        "too_many_attempts",
                        "blocked",
                        "not_found",
                        "already_used",
                        "phone_mismatch"
                      ],
                      "description": "Why verified is false."
                    },
                    "attempts_left": {
                      "type": "integer",
                      "description": "With wrong_code and phone_mismatch."
                    },
                    "test": {
                      "type": "boolean",
                      "description": "Present and true when the request was made with a test token."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_input, phone_required, invalid_phone or invalid_json.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_api_key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "not_enabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "too_many_requests.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/health": {
      "get": {
        "operationId": "health",
        "summary": "Service health",
        "security": [],
        "responses": {
          "200": {
            "description": "Service is up.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "ApiKey",
        "description": "Your Meser 10 API key (account settings, advanced). Server side only."
      },
      "VerifyToken": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Verify-Token",
        "description": "Access token issued by Meser 10 support (vt_...). Test tokens start with vt_test_."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "string",
            "enum": [
              "invalid_phone",
              "invalid_from",
              "invalid_template",
              "template_needs_one_code",
              "template_links_not_allowed",
              "template_not_allowed",
              "template_too_long",
              "too_soon",
              "phone_rate_limited",
              "account_daily_limit",
              "token_daily_limit",
              "too_many_requests",
              "too_many_failures",
              "temporarily_unavailable",
              "key_refused",
              "phone_required",
              "send_failed",
              "auth_failed",
              "sender_not_verified",
              "no_credits",
              "rejected",
              "upstream_blocked",
              "upstream_uncertain",
              "not_enabled",
              "invalid_api_key",
              "invalid_json",
              "payload_too_large",
              "forbidden",
              "invalid_autofill",
              "autofill_not_configured",
              "invalid_input",
              "method_not_allowed",
              "not_found",
              "internal_error"
            ]
          },
          "retry_after": {
            "type": "integer",
            "description": "Seconds to wait (too_soon, busy, temporarily_unavailable)."
          },
          "hint": {
            "type": "string"
          },
          "max": {
            "type": "integer",
            "description": "template_too_long: the limit in characters."
          },
          "id": {
            "type": "string",
            "description": "upstream_uncertain: the request id, still checkable."
          }
        }
      }
    }
  }
}