AI Using Claude, Cursor or Copilot?
Your coding assistant can do the whole integration. Paste this prompt into it. It points the assistant to llms-full.txt, a single file with everything it needs: how to detect your SMS provider, the code to add, the API reference and a checklist.
Integrate otpmock into this project's test environment so our end-to-end tests can read SMS verification codes. First read https://otpmock.com/llms-full.txt and follow it exactly. Only change test, CI and staging configuration; production must keep using the real SMS provider. Read the API key from the OTPMOCK_API_KEY environment variable and never hard-code or commit it.Machine-readable docs: /llms.txt · /llms-full.txt
Quickstart
-
Get an API key
Create an account and copy your key from the dashboard. Treat it like a password: it reads every message sent to your inbox.
In the environment where your tests and your test deployment run, set:
.env.testOTPMOCK_URL=https://api.otpmock.com OTPMOCK_API_KEY=om_live_... -
Send your app's SMS to otpmock
In the test environment only, point your SMS provider's SDK at otpmock and use your otpmock API key as the provider credential. See Providers.
-
Read the code in your test
Use the test helper or call
GET /v1/inbox/{phone}/codeyourself.
Authentication
Every API request needs your API key. Two forms are accepted:
- Bearer token, for the otpmock API:
Authorization: Bearer om_live_... - Provider-native credentials: HTTP Basic (Twilio, Vonage, Plivo, Netgsm),
App(Infobip), the AWS SigV4 access key ID (AWS SNS), the Vonage JWTapplication_id, or the bodykey(İleti Merkezi). Each provider page says which field takes your otpmock key.
Requests without a valid key get 401. On Twilio-compatible paths the body follows Twilio's error format (code 20003).
Providers
otpmock emulates these SMS provider APIs with their real paths, payloads, responses and errors. Each is tested with the provider's official Node.js SDK and needs one configuration option in your test environment. Open a provider for its exact setup.
| Provider | Emulated endpoints | Setup |
|---|---|---|
Twiliotwilio | POST /2010-04-01/Accounts/{AccountSid}/Messages.jsonPOST /v2/Services/{ServiceSid}/VerificationsPOST /v2/Services/{ServiceSid}/VerificationCheck | Twilio guide |
Vonage@vonage/server-sdk | POST /sms/jsonPOST /v1/messagesPOST /v2/verifyPOST /v2/verify/{request_id}DELETE /v2/verify/{request_id} | Vonage guide |
AWS SNS@aws-sdk/client-sns | POST / (Action=Publish, PhoneNumber, Message) | AWS SNS guide |
Telnyxtelnyx | POST /v2/messagesPOST /v2/verifications/smsPOST /v2/verifications/by_phone_number/{phone}/actions/verify | Telnyx guide |
AWS End User Messaging@aws-sdk/client-pinpoint-sms-voice-v2 | POST / (X-Amz-Target: PinpointSMSVoiceV2.SendTextMessage) | AWS End User Messaging guide |
Telesigntelesignsdk | POST /v1/messaging | Telesign guide |
Bandwidthbandwidth-sdk | POST /api/v2/users/{accountId}/messages | Bandwidth guide |
Infobip@infobip-api/sdk | POST /sms/2/text/advancedPOST /sms/3/messages | Infobip guide |
Sinch@sinch/sdk-core | POST /xms/v1/{service_plan_id}/batches | Sinch guide |
Bird (MessageBird)@messagebird/sdk | POST /v1/sms/messages | Bird (MessageBird) guide |
Plivoplivo | POST /v1/Account/{auth_id}/Message/ | Plivo guide |
Netgsm@netgsm/sms | POST /sms/rest/v2/sendPOST /sms/rest/v2/otp | Netgsm guide |
VerimorHTTP API | GET /v2/sendPOST /v2/send.json | Verimor guide |
MutlucellHTTP API | POST /smsgw-ws/sndblkex | Mutlucell guide |
İleti Merkezi@iletimerkezi/iletimerkezi-node | POST /v1/send-sms/json | İleti Merkezi guide |
Example: Twilio for Node.js
The official twilio package (v5) accepts a custom HTTP client. This one keeps every path and rewrites the host, so both api.twilio.com and verify.twilio.com traffic lands on otpmock.
curl -O https://otpmock.com/sdk/twilio-node-client.mjsimport twilio from "twilio";
import { OtpMockHttpClient } from "./twilio-node-client.mjs";
export const sms = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN, {
httpClient: process.env.OTPMOCK_URL ? new OtpMockHttpClient(process.env.OTPMOCK_URL) : undefined,
});In the test environment, set TWILIO_AUTH_TOKEN to your otpmock API key. sms.messages.create(...), sms.verify.v2.services(sid).verifications.create(...) and verificationChecks.create(...) then work unchanged.
Other languages
We test against each provider's Node.js SDK. In other languages, configure the provider client's host or base URL to https://api.otpmock.com and keep paths intact. For Twilio:
| Twilio host | Replace with |
|---|---|
https://api.twilio.com | https://api.otpmock.com |
https://verify.twilio.com | https://api.otpmock.com |
If your app calls an SMS provider we don't emulate yet, you can still send to POST /v1/messages/send from a small test-only adapter.
The test helper
A single file with no dependencies, in TypeScript or plain JavaScript. It works in Node 18+ and anywhere with fetch.
curl -O https://otpmock.com/sdk/otpmock.ts # TypeScript (Playwright)
curl -O https://otpmock.com/sdk/otpmock.mjs # JavaScript (Cypress, plain Node)| Method | What it does |
|---|---|
randomPhone(prefix?) | Returns a fresh fake number (default prefix +1555). Use one per test. |
waitForCode(phone, opts?) | Polls until a code arrives. Options: since (ms timestamp, default: 5 seconds before the call), timeout (default 15000), interval (default 300). |
clear(phone) | Deletes the number's messages and verifications. |
send(to, body) | Puts a message in the inbox yourself. Handy when testing the helper. |
Playwright
import { test as base } from "@playwright/test";
import { OtpMock } from "./otpmock";
export const test = base.extend<{ otp: OtpMock; phone: string }>({
otp: async ({}, use) => {
await use(new OtpMock({ baseUrl: process.env.OTPMOCK_URL!, apiKey: process.env.OTPMOCK_API_KEY! }));
},
phone: async ({ otp }, use) => {
const phone = otp.randomPhone();
await use(phone);
await otp.clear(phone);
},
});import { expect } from "@playwright/test";
import { test } from "./fixtures";
test("sign up with a phone number", async ({ page, otp, phone }) => {
await page.goto("/signup");
await page.getByLabel("Phone").fill(phone);
await page.getByRole("button", { name: "Send code" }).click();
const { code } = await otp.waitForCode(phone);
await page.getByLabel("Verification code").fill(code);
await expect(page.getByText("Welcome")).toBeVisible();
});Cypress
Cypress tests run in the browser, so call the helper from a Node task.
import { defineConfig } from "cypress";
import { OtpMock } from "./otpmock.mjs";
const otp = new OtpMock({ baseUrl: process.env.OTPMOCK_URL, apiKey: process.env.OTPMOCK_API_KEY });
export default defineConfig({
e2e: {
setupNodeEvents(on) {
on("task", { waitForCode: (phone) => otp.waitForCode(phone).then((r) => r.code) });
},
},
});it("signs up with a phone number", () => {
const phone = "+1555" + Cypress._.random(1e7, 9e7);
cy.visit("/signup");
cy.get("[name=phone]").type(phone);
cy.contains("Send code").click();
cy.task("waitForCode", phone).then((code) => cy.get("[name=code]").type(code));
cy.contains("Welcome").should("be.visible");
});API endpoints
Base URL: https://api.otpmock.com. Request bodies can be JSON or form-encoded. Responses are JSON. Timestamps are milliseconds since the Unix epoch.
/v1/messages/sendPut a message in the inbox. Fields: to and body (required), from (optional). Returns 201.
curl https://api.otpmock.com/v1/messages/send \
-H "Authorization: Bearer $OTPMOCK_API_KEY" \
-H "Content-Type: application/json" \
-d '{"to": "+15550142", "body": "Your code is 4819"}'{
"sid": "SM3f1c…",
"to": "+15550142",
"from": null,
"body": "Your code is 4819",
"code": "4819",
"source": "api",
"receivedAt": 1791406301420,
"status": "queued"
}/v1/inbox/{phone}/codeThe most recent code sent to {phone}, or 404 with {"error": "no_code_yet"}. Pass ?since= (ms) to ignore anything older, which is how you avoid reading a code from a previous run.
{ "code": "4819", "messageSid": "SM3f1c…", "body": "Your code is 4819", "receivedAt": 1791406301420 }/v1/inbox/{phone}/messagesMessages for one number, newest first, as {"messages": [...]}. ?limit= from 1 to 200, default 50.
/v1/messagesEvery message in your inbox, newest first. Same shape and limit as above. This is what the live inbox uses.
/v1/inbox/{phone}Deletes the number's messages and verifications. Returns {"deleted": n} with the number of messages removed.
Provider-compatible endpoints
Every provider in the table above is served at https://api.otpmock.com with its own paths, parameters, response shapes and errors, so the official SDKs call them unchanged. Each provider page lists its endpoints. Twilio's, as an example:
/2010-04-01/Accounts/{AccountSid}/Messages.jsonParameters To, Body, and optionally From or MessagingServiceSid. Returns 201 with a Twilio Message resource (sid starting with SM, status: "queued"). Missing To gives error 21604; missing Body gives 21602.
/v2/Services/{ServiceSid}/VerificationsParameters To and Channel (default sms). otpmock generates a 6-digit code, puts "Your verification code is: 123456" in the inbox, and returns 201 with status: "pending". Starting a new verification cancels the previous pending one for that number.
/v2/Services/{ServiceSid}/VerificationCheckParameters To and Code. The right code returns status: "approved" and valid: true. A wrong code returns status: "pending" and valid: false; the fifth wrong attempt cancels the verification. With no pending verification you get 404 and Twilio error 20404, which is also what happens if you check an approved code twice.
Phone numbers & codes
- Numbers are stored in E.164 form:
+1 (555) 014-2,15550142and+15550142are the same inbox, so providers that drop the+still match. Turkish local numbers from Netgsm, İleti Merkezi, Verimor and Mutlucell (5321234567) are stored as+905321234567. - In URLs, write
+as%2B. A literal+in the path also works. - Codes are 4 to 8 digits. otpmock first looks for one near a keyword (code, OTP, PIN, passcode, verification, and their Turkish equivalents), then falls back to the first 4 to 8 digit number in the text. Messages without one are stored with
"code": null.
Limits & retention
| What | Limit |
|---|---|
| Message retention | 10 minutes, then deleted automatically |
| Monthly messages | Depends on your plan, counted per calendar month (UTC). Each SMS sent and each Verify verification started counts as one. Reading is free. |
| Over the limit | Send endpoints return 429 until the 1st of next month (UTC) or an upgrade. Reads keep working. |
| Verify check attempts | 5 per verification |
| List page size | 200 messages |
Errors
| Status | Meaning |
|---|---|
400 | A required field is missing. Twilio paths include a Twilio error code. |
401 | Missing or invalid API key. |
404 | No code yet, no pending verification, or unknown path. |
429 | Monthly message allowance used up. |