Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: N0t1fy Dev Verifications API
version: 0.2.0
description: 'Operations tagged verifications across 2 of this provider''s published API definitions: n0t1fy-dev-api-openapi.json, n0t1fy-dev-openapi.json. Each path carries the servers of the definition it was published in.'
tags:
- name: verifications
paths:
/v1/verifications:
post:
tags:
- verifications
summary: 'Start a verification: NOTIFY makes a code and sends it by SMS'
description: 'NOTIFY makes a random six-digit code, sends it to `to` with one of the code templates, and answers with the verification. The code is never in any answer, log or table: only a keyed hash of it is kept, until the verification ends (at most about an hour after the code expires). It works for 10 minutes and 5 checks, once. A new start for the same number replaces the previous code.
With a test key, only the test numbers +995512345670 to +995512345679 are accepted, and their code is always `000000`; nothing is sent or charged. A live key sends a real SMS and spends one credit.
`201` with `status: "pending"`: the code was sent (or may have been: read `message.status`); show the code-entry field at once. `201` with `status: "failed"`: the provider refused the SMS; `error` says why. Rarely, `201` with `status: "canceled"`: a newer start for the same number, or a claim, ended this code while the start was in flight, and a replayed start can even show `approved`: act only on a check''s `200`. Requires an `Idempotency-Key`: replaying it with the identical body returns the original verification with `200` (it may have been approved since, which is not a check result), and reusing it with a different body returns `409`. Then check the code the user types with POST /v1/verifications/{id}/check.'
operationId: start_verification_route_v1_verifications_post
parameters:
- name: Idempotency-Key
in: header
required: true
schema:
type: string
minLength: 1
maxLength: 200
title: Idempotency-Key
description: One unique value per logical message or verification start, such as a UUID. Reuse it only to retry the identical request.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/StartVerificationRequest'
responses:
'201':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/VerificationResponse'
'200':
description: 'Idempotent replay: the original verification, as it is now.'
content:
application/json:
schema:
$ref: '#/components/schemas/VerificationResponse'
'400':
description: 'Invalid body, recipient or template, or a missing Idempotency-Key. `test_number_required`: a test key with a number that is not a test number.'
content:
application/json:
schema:
$ref: '#/components/schemas/ApiErrorResponse'
'401':
description: Missing, malformed, unknown, or revoked API key.
content:
application/json:
schema:
$ref: '#/components/schemas/ApiErrorResponse'
'403':
description: The project is suspended.
content:
application/json:
schema:
$ref: '#/components/schemas/ApiErrorResponse'
'402':
description: '`insufficient_credits`: a live key''s project has no credit left for the SMS, or its subscription has ended. Nothing was sent.'
content:
application/json:
schema:
$ref: '#/components/schemas/ApiErrorResponse'
'409':
description: The Idempotency-Key was reused with a different body.
content:
application/json:
schema:
$ref: '#/components/schemas/ApiErrorResponse'
'429':
description: '`too_many_verifications`: this number had 5 verifications in the last hour or 10 in the last 24 hours from this project; the message says when the next is allowed. `rate_limited`: a per-minute limit is used up (5 sends a minute to one number, 60 sends a minute per project); the message names it. `daily_limit_reached`: the free tier''s daily limit. Nothing was sent. Every `429` carries a `Retry-After` header (whole seconds, rounded up) and `error.retry_at` (ISO 8601 UTC): the same moment, when the limit frees.'
content:
application/json:
schema:
$ref: '#/components/schemas/ApiErrorResponse'
headers:
Retry-After:
description: Whole seconds until the limit frees, rounded up and at least 1.
schema:
type: integer
minimum: 1
'503':
description: '`provider_unavailable`: the provider was unreachable; nothing was sent, and it is safe to retry with the same Idempotency-Key. `verifications_disabled`: the operator switched verifications off; nobody is let in until they are back.'
content:
application/json:
schema:
$ref: '#/components/schemas/ApiErrorResponse'
security:
- bearerAuth: []
/v1/verifications/{verification_id}/check:
post:
tags:
- verifications
summary: Check the code a user typed
description: 'Answers `200` with the verification, `status: "approved"`, only when the code is right, for the number the code was sent to, within 10 minutes, with fewer than 5 wrong attempts before it, and never approved before. Every other answer is an error. Let the user in only when a check answers `200` with status `approved`; treat every other answer, a timeout and a network error as not approved. Branch on `error.code`, never on the HTTP status alone.
`to` must be the number your application is about to mark as verified. Every check spends one attempt, including a wrong number; a malformed `code` (not six digits as a string) or `to` spends none. A check is not idempotent: if its answer is lost, the attempt may have approved the code, which then cannot be used again, so ask the user for a new code. Do not retry a check by itself.'
operationId: check_verification_route_v1_verifications__verification_id__check_post
parameters:
- name: verification_id
in: path
required: true
schema:
type: string
title: Verification Id
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CheckVerificationRequest'
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/ApprovedVerificationResponse'
'400':
description: '`invalid_request`: a malformed body, or a `code` that is not six digits written as a string. `invalid_recipient`: `to` is not +9955 and eight digits. No attempt was spent.'
content:
application/json:
schema:
$ref: '#/components/schemas/ApiErrorResponse'
'401':
description: Missing, malformed, unknown, or revoked API key.
content:
application/json:
schema:
$ref: '#/components/schemas/ApiErrorResponse'
'403':
description: The project is suspended.
content:
application/json:
schema:
$ref: '#/components/schemas/ApiErrorResponse'
'404':
description: '`verification_not_found`: no such verification in this project.'
content:
application/json:
schema:
$ref: '#/components/schemas/ApiErrorResponse'
'409':
description: '`verification_closed`: already approved, replaced by a newer code, or its SMS was never sent. Offer a new code.'
content:
application/json:
schema:
$ref: '#/components/schemas/ApiErrorResponse'
'410':
description: '`verification_expired`: more than 10 minutes since the start.'
content:
application/json:
schema:
$ref: '#/components/schemas/ApiErrorResponse'
'422':
description: '`incorrect_code`: a wrong code; the message says how many attempts are left. `too_many_attempts`: a wrong code that was the fifth, or any check after it: offer a new code. `recipient_mismatch`: `to` is not the number the code was sent to; the attempt is spent and the code was not compared.'
content:
application/json:
schema:
$ref: '#/components/schemas/ApiErrorResponse'
'429':
description: '`rate_limited`: more than 120 checks a minute in this project (test and live keys counted apart). No attempt was spent. Every `429` carries a `Retry-After` header (whole seconds, rounded up) and `error.retry_at` (ISO 8601 UTC): the same moment, when the limit frees.'
content:
application/json:
schema:
$ref: '#/components/schemas/ApiErrorResponse'
headers:
Retry-After:
description: Whole seconds until the limit frees, rounded up and at least 1.
schema:
type: integer
minimum: 1
'503':
description: '`verifications_disabled`: the operator switched verifications off; nobody is let in until they are back.'
content:
application/json:
schema:
$ref: '#/components/schemas/ApiErrorResponse'
security:
- bearerAuth: []
components:
schemas:
CheckVerificationRequest:
properties:
to:
type: string
title: To
description: 'The number your application is about to mark as verified, in E.164 format. It must be the number the verification was started for: otherwise the answer is `recipient_mismatch`, and the attempt is spent.'
examples:
- '+995591234567'
code:
type: string
title: Code
description: 'The code the user typed: six digits, as a JSON string, so a leading zero is never lost. Surrounding spaces are trimmed. Anything else is refused with `400` and spends no attempt.'
examples:
- '482913'
additionalProperties: false
type: object
required:
- to
- code
title: CheckVerificationRequest
examples:
- code: '482913'
to: '+995591234567'
VerificationResponse:
properties:
id:
type: string
title: Id
description: 'Opaque verification identifier: the check needs it.'
object:
type: string
const: verification
title: Object
description: Always `verification`.
to:
type: string
title: To
description: 'Masked number: country code and first digit, then the last two digits.'
examples:
- +9955••••••67
status:
type: string
enum:
- pending
- approved
- failed
- expired
- canceled
title: Status
description: '`pending` until a check approves it or it ends. Only `pending` changes; a `pending` verification past `expires_at` is shown as `expired`. Let a user in only on a check that answers `200` with `approved`, never on this field read elsewhere: a replayed start can show `approved` too.'
template:
type: string
title: Template
description: The template of the SMS that carried the code.
locale:
type: string
enum:
- ka
- en
title: Locale
description: The language of that SMS.
attempts_remaining:
type: integer
title: Attempts Remaining
description: 'Checks left before the code stops working: 5 at the start, one fewer for each wrong code, and 0 once the verification is no longer `pending` (approved, expired, canceled, or ended by wrong codes).'
examples:
- 5
expires_at:
type: string
title: Expires At
description: 'ISO 8601 UTC: 10 minutes after the start request. No check works after it.'
approved_at:
anyOf:
- type: string
- type: 'null'
title: Approved At
description: When a check approved it, or `null`.
error:
anyOf:
- $ref: '#/components/schemas/VerificationError'
- type: 'null'
description: '`null` unless it ended without an approval (not for `expired`).'
message:
$ref: '#/components/schemas/MessageResponse'
description: The SMS that carried the code. Read its delivery with GET /v1/messages/{id}; show the code-entry field at once, without waiting for `delivered`.
created_at:
type: string
title: Created At
description: ISO 8601 UTC, second precision, `Z` suffix.
updated_at:
type: string
title: Updated At
description: ISO 8601 UTC time of the most recent change.
type: object
required:
- id
- object
- to
- status
- template
- locale
- attempts_remaining
- expires_at
- approved_at
- message
- created_at
- updated_at
title: VerificationResponse
description: One verification. The code, its hash and the full number are never included.
examples:
- attempts_remaining: 5
created_at: '2026-10-20T09:15:30Z'
expires_at: '2026-10-20T09:25:30Z'
id: ver_01J8XW7K2N9P4Q6R8S0T2V4W6X
locale: ka
message:
created_at: '2026-10-20T09:15:30Z'
id: msg_01J8XW7K3B5D7F9H1K3M5P7R9T
object: message
segments: 1
status: submitted
to: +9955••••••67
updated_at: '2026-10-20T09:15:31Z'
object: verification
status: pending
template: verification_code
to: +9955••••••67
updated_at: '2026-10-20T09:15:31Z'
MessageError:
properties:
code:
type: string
title: Code
description: Stable machine-readable code, safe to branch on.
examples:
- provider_rejected
message:
type: string
title: Message
description: Human-readable explanation. Wording may change; the code will not.
examples:
- The delivery provider rejected the message.
type: object
required:
- code
- message
title: MessageError
description: A safe, provider-neutral explanation attached to a message.
StartVerificationRequest:
properties:
to:
type: string
title: To
description: 'The phone number to verify, in E.164 format: `+9955` followed by eight digits. With a test key (`sk_test_`), only the test numbers +995512345670 to +995512345679 are accepted, and their code is always `000000`.'
examples:
- '+995591234567'
template:
type: string
enum:
- verification_code
- login_code
- password_reset_code
title: Template
description: Which message carries the code. NOTIFY fills in the code, and the minutes where the template names them (10).
default: verification_code
examples:
- verification_code
locale:
type: string
enum:
- ka
- en
title: Locale
description: 'Language of the message: `ka` (Georgian, the default) or `en`.'
default: ka
examples:
- ka
additionalProperties: false
type: object
required:
- to
title: StartVerificationRequest
examples:
- locale: ka
template: verification_code
to: '+995591234567'
ApiErrorResponse:
properties:
error:
$ref: '#/components/schemas/ApiErrorBody'
type: object
required:
- error
title: ApiErrorResponse
description: Every error response in this API has this shape.
ApiErrorBody:
properties:
code:
type: string
title: Code
description: Stable machine-readable error code.
examples:
- invalid_recipient
message:
type: string
title: Message
description: Human-readable explanation, written to be actionable.
examples:
- 'The recipient must be a Georgian mobile number in E.164 format: +9955XXXXXXXX.'
request_id:
anyOf:
- type: string
- type: 'null'
title: Request Id
description: Echoes the `X-Request-Id` response header; quote it when reporting a problem.
examples:
- req_01J8XW7K2N9P4Q6R8S0T2V4W6X
retry_at:
anyOf:
- type: string
- type: 'null'
title: Retry At
description: 'Only on a `429` (`rate_limited`, `daily_limit_reached`, `too_many_verifications`): when the limit frees, ISO 8601 UTC with a `Z` suffix, the same moment the `Retry-After` header counts down to. Show a countdown to it; do not retry before it. Absent from every other error.'
examples:
- '2026-10-16T12:35:00Z'
type: object
required:
- code
- message
title: ApiErrorBody
MessageResponse:
properties:
id:
type: string
title: Id
description: Opaque message identifier. Store it to read the status later.
object:
type: string
const: message
title: Object
description: Always `message`.
to:
type: string
title: To
description: 'Masked recipient: country code and first digit, then the last two digits.'
examples:
- +9955••••••67
status:
type: string
enum:
- queued
- submitted
- delivered
- undeliverable
- failed
- unknown
title: Status
description: Normalized delivery state. `submitted` means the provider accepted the request, not that a handset received it.
segments:
anyOf:
- type: integer
- type: 'null'
title: Segments
description: SMS segments this message occupies — the unit the provider bills. `null` only for messages created before segment counting existed.
examples:
- 1
error:
anyOf:
- $ref: '#/components/schemas/MessageError'
- type: 'null'
description: '`null` unless the message failed or was undeliverable.'
created_at:
type: string
title: Created At
description: ISO 8601 UTC, second precision, `Z` suffix.
examples:
- '2026-09-14T09:15:30Z'
updated_at:
type: string
title: Updated At
description: ISO 8601 UTC time of the most recent change.
examples:
- '2026-09-14T09:15:31Z'
type: object
required:
- id
- object
- to
- status
- segments
- created_at
- updated_at
title: MessageResponse
description: One message resource. Text, full recipient, and provider internals are never included.
examples:
- created_at: '2026-09-14T09:15:30Z'
id: msg_01J8XW7K2N9P4Q6R8S0T2V4W6X
object: message
segments: 1
status: submitted
to: +9955••••••67
updated_at: '2026-09-14T09:15:31Z'
ApprovedVerificationResponse:
properties:
id:
type: string
title: Id
description: 'Opaque verification identifier: the check needs it.'
object:
type: string
const: verification
title: Object
description: Always `verification`.
to:
type: string
title: To
description: 'Masked number: country code and first digit, then the last two digits.'
examples:
- +9955••••••67
status:
type: string
const: approved
title: Status
description: 'Always `approved`: every other outcome of a check is an error.'
template:
type: string
title: Template
description: The template of the SMS that carried the code.
locale:
type: string
enum:
- ka
- en
title: Locale
description: The language of that SMS.
attempts_remaining:
type: integer
title: Attempts Remaining
description: 'Checks left before the code stops working: 5 at the start, one fewer for each wrong code, and 0 once the verification is no longer `pending` (approved, expired, canceled, or ended by wrong codes).'
examples:
- 5
expires_at:
type: string
title: Expires At
description: 'ISO 8601 UTC: 10 minutes after the start request. No check works after it.'
approved_at:
type: string
title: Approved At
description: 'ISO 8601 UTC: when this check approved it.'
error:
anyOf:
- $ref: '#/components/schemas/VerificationError'
- type: 'null'
description: '`null` unless it ended without an approval (not for `expired`).'
message:
$ref: '#/components/schemas/MessageResponse'
description: The SMS that carried the code. Read its delivery with GET /v1/messages/{id}; show the code-entry field at once, without waiting for `delivered`.
created_at:
type: string
title: Created At
description: ISO 8601 UTC, second precision, `Z` suffix.
updated_at:
type: string
title: Updated At
description: ISO 8601 UTC time of the most recent change.
type: object
required:
- id
- object
- to
- status
- template
- locale
- attempts_remaining
- expires_at
- approved_at
- message
- created_at
- updated_at
title: ApprovedVerificationResponse
description: 'The only success a check has. As the check''s response model it makes the rule
"a 2xx is only an approval" the code''s own: anything else answers 500, not 200
(the 2026-10-15 checkpoint, item 4). The MCP check tool validates through it too.'
examples:
- attempts_remaining: 5
created_at: '2026-10-20T09:15:30Z'
expires_at: '2026-10-20T09:25:30Z'
id: ver_01J8XW7K2N9P4Q6R8S0T2V4W6X
locale: ka
message:
created_at: '2026-10-20T09:15:30Z'
id: msg_01J8XW7K3B5D7F9H1K3M5P7R9T
object: message
segments: 1
status: submitted
to: +9955••••••67
updated_at: '2026-10-20T09:15:31Z'
object: verification
status: pending
template: verification_code
to: +9955••••••67
updated_at: '2026-10-20T09:15:31Z'
VerificationError:
properties:
code:
type: string
title: Code
description: '`too_many_attempts`, `replaced` (a newer code was sent to the number), `project_claimed`, or the error of the SMS that carried the code when it was never sent (`provider_rejected`, `provider_unavailable`, …).'
examples:
- replaced
message:
type: string
title: Message
description: Human-readable explanation. Wording may change.
type: object
required:
- code
- message
title: VerificationError
description: Why a verification ended without an approval.
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: 'An API key: `sk_test_…` (fake provider, never sends) or `sk_live_…`. Get a test key from `POST /v1/test-keys`.'
x-refined-from:
- n0t1fy-dev-api-openapi.json
- n0t1fy-dev-openapi.json