One-time codes.
Send a one-time code by email or SMS, with risk scoring, and check what the user typed. Every request needs an API key in the Authorization header; the reference overview covers keys, errors and rate limits.
Send a one-time code
POST/v1/verify/sendSending or full access key
Create a one-time code and send it by email or SMS.
Risk scoring
Every request is scored before anything is sent. The response carries a risk object with the score, the level and one entry per signal that fired. Pass clientIp when you have it, so the per IP address signal can contribute. Notix stores only a keyed hash of that address and never logs it.
SMS codes
Pass channel: "sms" to deliver the code by SMS instead of email.
{
"to": "+2348012345678",
"channel": "sms",
"appName": "Acme"
}
For channel: "sms", to is a phone number in E.164 form and appName is required. senderId is optional and defaults to your team's own approved sender ID for the country, or to the shared Notix sender ID when you have none (see SMS and sender IDs). SMS codes are prepaid from your wallet and need no paid plan.
from, subject and templateId do not apply to channel: "sms" and are rejected. The default text is "<appName>: your code is <code>. It expires in <minutes> minutes.".
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | BAD_REQUEST | The request is invalid: codeLength, expiresIn, the to address, an unverified from domain, or a suppressed recipient. |
| 401 | UNAUTHORIZED | The API key is missing or not valid. |
| 402 | INSUFFICIENT_BALANCE | The team wallet cannot cover an SMS code. Nothing was sent. |
| 403 | FORBIDDEN | The API key is restricted to a domain other than the resolved from address. |
| 404 | NOT_FOUND | The calling team no longer exists. |
| 422 | RISK_REFUSED | Risk scoring refused the request. Nothing was sent and no code exists. |
| 429 | RATE_LIMITED | Too many codes were requested for this recipient. |
| Field | Type | About |
|---|---|---|
torequired | string | The recipient email address, or for channel sms a phone number in E.164 form. At least 3 characters. At most 320 characters. |
channel | string | Where the code is delivered. Default email. For sms, appName is required, from, subject and templateId are not allowed, and to must be a phone number with its country code. One of |
senderId | string | For channel sms only. A sender ID approved for this team. Defaults to the team's own approved sender ID for the country, or to the shared Notix sender ID when it has none. At most 11 characters. |
from | string | Address on a domain verified by the team. 'Name <address>' is allowed. Defaults to no-reply@ the team's first verified domain. |
appName | string | Shown in the email. Defaults to the team name. At most 60 characters. |
codeLength | integer | Digits in the generated code. 4 to 8, default 6. Minimum 4. Maximum 8. |
expiresIn | integer | Seconds until the code expires. 60 to 1800, default 600. Minimum 60. Maximum 1800. |
templateId | string | A Notix template whose subject and body use {{code}}, {{appName}} and {{expiresMinutes}}. When absent, the built-in verification template is used. |
subject | string | Overrides the built-in template's subject. Default '{{appName}} verification code: {{code}}'. At most 998 characters. |
clientIp | string (ip) | The IPv4 or IPv6 address the end user made the request from. Used by risk scoring. Notix stores only a keyed hash of it and never logs it. |
curl -X POST "https://app.usenotix.dev/api/v1/verify/send" \
-H "Authorization: Bearer $NOTIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "user@example.com"
}'
import { Notix } from "notix-js";
const notix = new Notix(process.env.NOTIX_API_KEY);
const { data, error } = await notix.verify.send({
to: "user@example.com",
appName: "Acme",
clientIp: "203.0.113.10",
});
from notix import Notix
notix = Notix() # reads NOTIX_API_KEY
verification, _ = notix.verify.send({"to": "user@example.com", "appName": "Acme", "clientIp": "203.0.113.10"})
use Notix\Notix;
$notix = new Notix(getenv('NOTIX_API_KEY'));
$verification = $notix->verify->send(['to' => 'user@example.com', 'appName' => 'Acme'], '203.0.113.10');
Responses
201The verification was created and the code was emailed
| Field | Type | About |
|---|---|---|
idrequired | string | |
torequired | string | |
statusrequired | string | One of |
expiresAtrequired | string | |
attemptsRemainingrequired | number | Wrong-code guesses left before the code locks out. A correct check never reduces this value, even one made after earlier wrong guesses; only a wrong guess does. |
emailIdrequired | string | Can be null. |
channelrequired | string | One of |
providerMessageIdrequired | string | The delivery provider's message id for channel sms. Null for email and until the message is handed to the provider. Can be null. |
deliveryStatusrequired | string | SMS delivery state. Null for email. One of |
deliveryReasonrequired | string | A plain-language reason when SMS delivery failed. Null otherwise. Can be null. |
risk | object | |
risk.scorerequired | number | Summed signal weights, capped at 100. |
risk.levelrequired | string | The band the score falls into, using the thresholds Notix has configured. One of |
risk.reasonsrequired | object[] | One entry per signal that fired. |
risk.reasons[].coderequired | string | |
risk.reasons[].detailrequired | string | |
verified | boolean | |
reason | string | One of |
400Invalid input, for example codeLength, expiresIn, an invalid 'to' address, an unverified from domain, a suppressed recipient, or for channel sms a missing appName, an email-only field (from, subject, templateId), or a 'to' that is not a phone number in E.164 form
402Channel sms only: the team wallet cannot cover the message. The body carries code INSUFFICIENT_BALANCE.
403The calling API key is restricted to a domain other than the resolved 'from' address
404The calling team no longer exists
422Risk scoring refused the request. The body carries code RISK_REFUSED and the risk object naming every signal that fired. Nothing was sent and no code exists.
| Field | Type | About |
|---|---|---|
errorrequired | object | |
error.coderequired | string | One of |
error.messagerequired | string | |
riskrequired | object | |
risk.scorerequired | number | Summed signal weights, capped at 100. |
risk.levelrequired | string | The band the score falls into, using the thresholds Notix has configured. One of |
risk.reasonsrequired | object[] | One entry per signal that fired. |
risk.reasons[].coderequired | string | |
risk.reasons[].detailrequired | string |
429Too many verification codes requested for this recipient
Check a one-time code
POST/v1/verify/checkSending or full access key
Check the code a user typed against a verification. The response is the verification's state after this check.
| Field | Type | About |
|---|---|---|
idrequired | string | The verification id returned by /v1/verify/send |
coderequired | string | The code the user typed |
curl -X POST "https://app.usenotix.dev/api/v1/verify/check" \
-H "Authorization: Bearer $NOTIX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"id": "ver_abc123",
"code": "123456"
}'
import { Notix } from "notix-js";
const notix = new Notix(process.env.NOTIX_API_KEY);
const { data, error } = await notix.verify.check({ id: "ver_123", code: "123456" });
if (data?.verified) {
// The code matched.
}
from notix import Notix
notix = Notix() # reads NOTIX_API_KEY
result, _ = notix.verify.check({"id": "ver_123", "code": "123456"})
use Notix\Notix;
$notix = new Notix(getenv('NOTIX_API_KEY'));
$result = $notix->verify->check(['id' => 'ver_123', 'code' => '123456']);
Responses
200The current state of the verification, after this check
| Field | Type | About |
|---|---|---|
idrequired | string | |
torequired | string | |
statusrequired | string | One of |
expiresAtrequired | string | |
attemptsRemainingrequired | number | Wrong-code guesses left before the code locks out. A correct check never reduces this value, even one made after earlier wrong guesses; only a wrong guess does. |
emailIdrequired | string | Can be null. |
channelrequired | string | One of |
providerMessageIdrequired | string | The delivery provider's message id for channel sms. Null for email and until the message is handed to the provider. Can be null. |
deliveryStatusrequired | string | SMS delivery state. Null for email. One of |
deliveryReasonrequired | string | A plain-language reason when SMS delivery failed. Null otherwise. Can be null. |
risk | object | |
risk.scorerequired | number | Summed signal weights, capped at 100. |
risk.levelrequired | string | The band the score falls into, using the thresholds Notix has configured. One of |
risk.reasonsrequired | object[] | One entry per signal that fired. |
risk.reasons[].coderequired | string | |
risk.reasons[].detailrequired | string | |
verified | boolean | |
reason | string | One of |
400id or code is missing
404No verification with this id for the calling team
Get a verification
GET/v1/verify/{id}Sending or full access key
Retrieve the current state of a verification without changing it.
A verification that risk scoring refused has status refused. Its risk object lists the codes that fired, each with a general description of the signal.
| Field | Type | About |
|---|---|---|
idrequired | string |
curl -X GET "https://app.usenotix.dev/api/v1/verify/<id>" \
-H "Authorization: Bearer $NOTIX_API_KEY"
import { Notix } from "notix-js";
const notix = new Notix(process.env.NOTIX_API_KEY);
const { data, error } = await notix.verify.get("ver_123");
from notix import Notix
notix = Notix() # reads NOTIX_API_KEY
verification, _ = notix.verify.get("ver_123")
use Notix\Notix;
$notix = new Notix(getenv('NOTIX_API_KEY'));
$verification = $notix->verify->get('ver_123');
Responses
200The current state of the verification
| Field | Type | About |
|---|---|---|
idrequired | string | |
torequired | string | |
statusrequired | string | One of |
expiresAtrequired | string | |
attemptsRemainingrequired | number | Wrong-code guesses left before the code locks out. A correct check never reduces this value, even one made after earlier wrong guesses; only a wrong guess does. |
emailIdrequired | string | Can be null. |
channelrequired | string | One of |
providerMessageIdrequired | string | The delivery provider's message id for channel sms. Null for email and until the message is handed to the provider. Can be null. |
deliveryStatusrequired | string | SMS delivery state. Null for email. One of |
deliveryReasonrequired | string | A plain-language reason when SMS delivery failed. Null otherwise. Can be null. |
risk | object | |
risk.scorerequired | number | Summed signal weights, capped at 100. |
risk.levelrequired | string | The band the score falls into, using the thresholds Notix has configured. One of |
risk.reasonsrequired | object[] | One entry per signal that fired. |
risk.reasons[].coderequired | string | |
risk.reasons[].detailrequired | string | |
verified | boolean | |
reason | string | One of |