Notix
API reference

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.

json
{
  "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

StatusCodeMeaning
400BAD_REQUESTThe request is invalid: codeLength, expiresIn, the to address, an unverified from domain, or a suppressed recipient.
401UNAUTHORIZEDThe API key is missing or not valid.
402INSUFFICIENT_BALANCEThe team wallet cannot cover an SMS code. Nothing was sent.
403FORBIDDENThe API key is restricted to a domain other than the resolved from address.
404NOT_FOUNDThe calling team no longer exists.
422RISK_REFUSEDRisk scoring refused the request. Nothing was sent and no code exists.
429RATE_LIMITEDToo many codes were requested for this recipient.
Request body (JSON)
FieldTypeAbout
torequiredstring

The recipient email address, or for channel sms a phone number in E.164 form.

At least 3 characters. At most 320 characters.

channelstring

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 email, sms. Default "email".

senderIdstring

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.

fromstring

Address on a domain verified by the team. 'Name <address>' is allowed. Defaults to no-reply@ the team's first verified domain.

appNamestring

Shown in the email. Defaults to the team name.

At most 60 characters.

codeLengthinteger

Digits in the generated code. 4 to 8, default 6.

Minimum 4. Maximum 8.

expiresIninteger

Seconds until the code expires. 60 to 1800, default 600.

Minimum 60. Maximum 1800.

templateIdstring

A Notix template whose subject and body use {{code}}, {{appName}} and {{expiresMinutes}}. When absent, the built-in verification template is used.

subjectstring

Overrides the built-in template's subject. Default '{{appName}} verification code: {{code}}'.

At most 998 characters.

clientIpstring (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.

terminal
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"
}'

Responses

201The verification was created and the code was emailed
201 response fields
FieldTypeAbout
idrequiredstring
torequiredstring
statusrequiredstring

One of pending, verified, expired, failed, refused.

expiresAtrequiredstring
attemptsRemainingrequirednumber

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.

emailIdrequiredstring

Can be null.

channelrequiredstring

One of email, sms.

providerMessageIdrequiredstring

The delivery provider's message id for channel sms. Null for email and until the message is handed to the provider.

Can be null.

deliveryStatusrequiredstring

SMS delivery state. Null for email.

One of queued, sent, delivered, failed, rejected. Can be null.

deliveryReasonrequiredstring

A plain-language reason when SMS delivery failed. Null otherwise.

Can be null.

riskobject
risk.scorerequirednumber

Summed signal weights, capped at 100.

risk.levelrequiredstring

The band the score falls into, using the thresholds Notix has configured.

One of LOW, MEDIUM, HIGH.

risk.reasonsrequiredobject[]

One entry per signal that fired.

risk.reasons[].coderequiredstring
risk.reasons[].detailrequiredstring
verifiedboolean
reasonstring

One of wrong_code, expired, already_used, too_many_attempts, refused.

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.
422 response fields
FieldTypeAbout
errorrequiredobject
error.coderequiredstring

One of RISK_REFUSED.

error.messagerequiredstring
riskrequiredobject
risk.scorerequirednumber

Summed signal weights, capped at 100.

risk.levelrequiredstring

The band the score falls into, using the thresholds Notix has configured.

One of LOW, MEDIUM, HIGH.

risk.reasonsrequiredobject[]

One entry per signal that fired.

risk.reasons[].coderequiredstring
risk.reasons[].detailrequiredstring
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.

Request body (JSON)
FieldTypeAbout
idrequiredstring

The verification id returned by /v1/verify/send

coderequiredstring

The code the user typed

terminal
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"
}'

Responses

200The current state of the verification, after this check
200 response fields
FieldTypeAbout
idrequiredstring
torequiredstring
statusrequiredstring

One of pending, verified, expired, failed, refused.

expiresAtrequiredstring
attemptsRemainingrequirednumber

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.

emailIdrequiredstring

Can be null.

channelrequiredstring

One of email, sms.

providerMessageIdrequiredstring

The delivery provider's message id for channel sms. Null for email and until the message is handed to the provider.

Can be null.

deliveryStatusrequiredstring

SMS delivery state. Null for email.

One of queued, sent, delivered, failed, rejected. Can be null.

deliveryReasonrequiredstring

A plain-language reason when SMS delivery failed. Null otherwise.

Can be null.

riskobject
risk.scorerequirednumber

Summed signal weights, capped at 100.

risk.levelrequiredstring

The band the score falls into, using the thresholds Notix has configured.

One of LOW, MEDIUM, HIGH.

risk.reasonsrequiredobject[]

One entry per signal that fired.

risk.reasons[].coderequiredstring
risk.reasons[].detailrequiredstring
verifiedboolean
reasonstring

One of wrong_code, expired, already_used, too_many_attempts, refused.

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.

Path parameters
FieldTypeAbout
idrequiredstring
terminal
curl -X GET "https://app.usenotix.dev/api/v1/verify/<id>" \
  -H "Authorization: Bearer $NOTIX_API_KEY"

Responses

200The current state of the verification
200 response fields
FieldTypeAbout
idrequiredstring
torequiredstring
statusrequiredstring

One of pending, verified, expired, failed, refused.

expiresAtrequiredstring
attemptsRemainingrequirednumber

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.

emailIdrequiredstring

Can be null.

channelrequiredstring

One of email, sms.

providerMessageIdrequiredstring

The delivery provider's message id for channel sms. Null for email and until the message is handed to the provider.

Can be null.

deliveryStatusrequiredstring

SMS delivery state. Null for email.

One of queued, sent, delivered, failed, rejected. Can be null.

deliveryReasonrequiredstring

A plain-language reason when SMS delivery failed. Null otherwise.

Can be null.

riskobject
risk.scorerequirednumber

Summed signal weights, capped at 100.

risk.levelrequiredstring

The band the score falls into, using the thresholds Notix has configured.

One of LOW, MEDIUM, HIGH.

risk.reasonsrequiredobject[]

One entry per signal that fired.

risk.reasons[].coderequiredstring
risk.reasons[].detailrequiredstring
verifiedboolean
reasonstring

One of wrong_code, expired, already_used, too_many_attempts, refused.

404No verification with this id for the calling team