X-Sender-Tenant + X-API-Key header pair. Neither endpoint uses a session or a bearer token.
Requests are validated strictly. Any body field not listed below is rejected
with
400, and only the first validation message is returned.Send a code
200 on success, not 201.
Body
string
required
Recipient mobile number, max 32 characters. Normalised to Israeli mobile
E.164 (
+9725XXXXXXXX). See Phone normalisation.string
default:"sms"
Delivery channel. One of
sms or voice. A voice call reads the code aloud
digit by digit.string
The branded sender ID to send from, max 11 characters. Matched
case insensitively against the workspace’s approved sender IDs. Optional in
the schema but required in practice: an empty value is rejected. See
Sender identity.
string
Message template, max 320 characters. Must contain the literal token
{{code}}, which is replaced with the generated code. If the token is
missing the platform default is used instead. Defaults to
Your verification code is {{code}}.Response
string
Opaque 21 character identifier. Pass it back to
/otp/verify. The code
itself is never returned.string
The channel the code was sent on:
sms or voice.integer
Code lifetime in seconds. Always
300 (5 minutes).integer
SMS segment count of the rendered message. This is the amount debited for a
voice send. An sms send is debited in billable credits instead, which can
differ. See Billing.Send errors
Errors use the standard envelope, so a coded failure looks like this:
On a
503 the wallet hold is released, so the send costs nothing. Nothing is
stored either, which means the verificationId is only ever issued after the
provider has accepted the message.Verify a code
Body
string
required
The identifier returned by
/otp/send, max 64 characters.string
required
The code the user entered, max 12 characters.
Response
boolean
true when the code matched. A match consumes the record, so the same
verificationId cannot be verified twice.string
Present only when
verified is false.Verify errors
Code lifetime and attempts
1
Generated
A 6 digit numeric code is generated with a cryptographic random source.
2
Stored as a hash
Only an HMAC digest of the code is stored, keyed to the workspace and the
verificationId. The code is never written to the response, to logs or to
the database.3
Expires after 5 minutes
The record has a 300 second lifetime. After that, verification returns
expired_or_unknown.4
At most 5 verify attempts
A
verificationId accepts 5 verification attempts. The 6th returns
too_many_attempts and the record is discarded. Comparison is constant
time.5
Single use
A successful verification deletes the record immediately. Re-submitting the
same
verificationId returns expired_or_unknown.Phone normalisation
phone is normalised before anything else happens. Spaces, hyphens, parentheses and dots are stripped, a leading 00 is treated as +, and the result must resolve to an Israeli mobile number in E.164 form (+9725 followed by 8 digits).
Anything else, including landlines and non-Israeli numbers, fails with
400 A valid mobile phone number is required before any provider call and before any wallet activity.
Sender identity
senderId is the branded sender name the SMS is sent from, and the caller ID a voice call originates from. It is resolved before the wallet is touched.
- The value must match one of the workspace’s active sender IDs. The match is case insensitive, and the stored casing is what the recipient sees.
- An empty value is rejected with
sender_id_required. There is no fallback to a shared default. - The reserved platform values
SENDER,INFOandSHOPare also rejected withsender_id_required. They are not available to workspace traffic. - An unrecognised value is rejected with
sender_id_not_approved.
GET /public/sms-sender-ids, which returns only approved, active sender IDs. Request a new sender ID from the Senderz app; approval is not part of this API.
Billing
Every send takes a hold on the workspace SMS credit wallet before dispatch. The hold is committed once the provider accepts the message, and released if the provider rejects or fails, so a503 costs nothing.
The
segments field in the response is always the segment count, so it equals the debit for voice but not necessarily for sms. Segment counting is GSM aware: 160 characters for a single GSM segment and 153 per segment beyond that, or 70 and 67 for messages containing non-GSM characters such as Hebrew or emoji.
A short default-length code message is one segment and one credit. Long custom message templates cost more, so keep them tight.
Billing is measured on the rendered message, after
{{code}} is
substituted. For a voice call the digits are spaced out before rendering,
which makes the voice body slightly longer than the SMS body for the same
template.Rate limits
Three independent limits apply to/otp/send.
/otp/verify is subject to the same 60 per minute route limit and the same 1200 per minute API key limit, and has no per number cap of its own.
Integration flow
1
Collect the number
Capture the mobile number and normalise it client side if you can, so an
invalid number does not consume a slot against the per number cap.
2
Send
Call
POST /otp/send and store the returned verificationId against the
user’s session server side. Never expose it in a URL.3
Collect the code
Prompt for the 6 digits. Allow a resend, but remember the per number cap
and that each resend issues a fresh
verificationId, invalidating none of
the earlier ones until they expire on their own.4
Verify
Call
POST /otp/verify with the stored verificationId. Treat
verified: false as a failure regardless of reason, and surface a
resend affordance on expired_or_unknown and too_many_attempts.Related
Authentication
The header pair both endpoints require.
Errors
The shared error envelope and status codes.
Resource dropdowns
Discover the approved sender IDs you may pass.
Messages
Transactional email and SMS to a contact.

