Send a one-time code

Create a one-time code and send it by email or SMS.

POSThttps://app.usenotix.dev/api/v1/verify/send

Full access and sending access keys can call this. Key permissions lists the extra rules some calls have.

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.

Idempotency

Pass an Idempotency-Key header to make a retry safe for 24 hours. The same key and body answers the original result without sending again, with the response header Idempotent-Replayed: true. The same key with a different body answers 409 NOT_UNIQUE; the same key while the first request is still running answers 409 too, so retry shortly. Keys are scoped to the API key. If the idempotency store is briefly unavailable the code is still sent, without de-duplication: a login code is never held back by an outage.

Dry run

Send the header Notix-Dry-Run: true to see what the request would do without doing it. The answer is 200 with dryRun: true: who it would reach, what they would see, what it would cost, a verdict (allowed and every reasons it would be refused for) and the limits it would count against. Nothing is sent, charged, reserved or written, and the call is recorded in the audit log as a dry run. A write route that cannot preview answers 400.

Body parameters

tostringrequired

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 or the SMS. May not contain a link or web address. Defaults to the team name for email; required for sms.

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.

Headers

Authorizationstringrequired

Bearer followed by your API key.

Idempotency-Keystring

Makes the request safe to retry for 24 hours. Same key and same body answers the original result without running again, with the response header Idempotent-Replayed: true. Same key and a different body answers 409 NOT_UNIQUE; same key while the first request is still running answers 409 too, so retry shortly. Keys are scoped to the API key.

Notix-Dry-Runstring

Set to true to preview the request instead of running it. The answer is 200 with dryRun: true: who it would reach, what they would see, what it would cost, whether policy allows it and the limits it counts against. Nothing is sent, charged, reserved or written. A write route that cannot preview answers 400.

Errors

400

Invalid 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

402

Channel sms only: the team wallet cannot cover the message. The body carries code INSUFFICIENT_BALANCE. The message names the shortfall only to a key a team admin or the owner made.

403

The calling API key is restricted to a domain other than the resolved 'from' address, or (channel sms) the key may not spend the wallet on SMS: it was created by a team member without admin rights, or it is a send-only key where those are not allowed SMS

404

The calling team no longer exists

422

Risk 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.

429

Too many verification codes requested for this recipient, or (channel sms) the team reached its daily SMS spending limit

Every error has the same shape. See all error codes.

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",
  "subject": "Your receipt for order 4471"
}'
Response200
{
  "dryRun": true,
  "kind": "string",
  "verdict": {
    "allowed": true,
    "reasons": [
      "string"
    ]
  },
  "checks": [
    "string"
  ]
}
Open in ClaudeOpen in ChatGPT