{"info":{"_postman_id":"1da462fa-59d1-4117-9a43-eb51efa7dd1e","name":"Public API Compacto OTP","description":"<html><head></head><body><h2 id=\"endpoints\">Endpoints</h2>\n<ul>\n<li><p>POST /otp/generate</p>\n</li>\n<li><p>POST /otp/validate</p>\n</li>\n</ul>\n<hr>\n<p>Welcome to the Compacto Public API documentation.<br>Here you will find all available public endpoints for generating and validating One-Time Passwords (OTPs).</p>\n<p>Each endpoint includes required headers, body format, examples, and possible error messages.</p>\n</body></html>","schema":"https://schema.getpostman.com/json/collection/v2.0.0/collection.json","toc":[],"owner":"36638207","collectionId":"1da462fa-59d1-4117-9a43-eb51efa7dd1e","publishedId":"2sB3HnMLco","public":true,"customColor":{"top-bar":"FFFFFF","right-sidebar":"303030","highlight":"FF6C37"},"publishDate":"2025-09-10T16:52:08.000Z"},"item":[{"name":"Public-generate-otp","id":"d320468e-36db-4ee9-b929-cb91fc7e5b0c","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"method":"POST","header":[{"key":"x-api-key","value":"{{YOUR_API_KEY}}","type":"text"},{"key":"Content-Type","value":"application/json","type":"text"}],"body":{"mode":"raw","raw":"{\r\n  \"channel\": \"email\",\r\n  \"provider\": \"generic\",\r\n  \"destination\": \"wajb96@gmail.com\",\r\n  \"type\": \"alphanumeric\",\r\n  \"length\": 8,\r\n  \"expiresInSeconds\": 300,\r\n  \"templateId\": \"otp-default-a1562ab2\"\r\n}\r\n","options":{"raw":{"language":"json"}}},"url":"https://cpt.cx/otp/generate","description":"<h4 id=\"description\"><strong>Description</strong></h4>\n<p>This endpoint generates a One-Time Password (OTP) and queues it for delivery via the selected channel.</p>\n<p>For security, the <strong>OTP value is never returned</strong>; only metadata and the <code>otpId</code> are provided.</p>\n<p><strong>Authentication</strong></p>\n<p>This endpoint requires an API Key in the request headers:</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">x-api-key: YOUR_API_KEY\nContent-Type: application/json\n\n</code></pre>\n<h5 id=\"request-body-json\">Request Body (JSON)</h5>\n<p>Required fields:</p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Field</th>\n<th>Type</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>channel</code></td>\n<td>string</td>\n<td>Delivery channel. Allowed values: <code>\"email\"</code>, <code>\"sms\"</code>.</td>\n</tr>\n<tr>\n<td><code>provider</code></td>\n<td>string</td>\n<td>Provider name previously configured in Compacto** (e.g., <code>\"generic\"</code>, <code>\"twilio\"</code>, <code>\"sendgrid\"</code>). Credentials/settings are managed server-side; clients send only the name. If the provider does not exist or isn’t configured for the client/channel, the API returns <strong>400</strong> with `Provider not found for this client and channel.</td>\n</tr>\n<tr>\n<td><code>destination</code></td>\n<td>string</td>\n<td>Recipient: an email address (for <code>email</code>) or an E.164 phone number (for <code>sms</code>).</td>\n</tr>\n<tr>\n<td><code>type</code></td>\n<td>string</td>\n<td>OTP type. Allowed values: <code>\"numeric\"</code>, <code>\"alphanumeric\"</code>.</td>\n</tr>\n<tr>\n<td><code>length</code></td>\n<td>number</td>\n<td>OTP length (e.g., <code>6</code> or <code>8</code>).</td>\n</tr>\n<tr>\n<td><code>expiresInSeconds</code></td>\n<td>number</td>\n<td>OTP time-to-live in seconds (e.g., <code>300</code>).</td>\n</tr>\n</tbody>\n</table>\n</div><h5 id=\"valid-request-example\">Valid Request Example</h5>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"channel\": \"email\",\n  \"provider\": \"generic\",\n  \"destination\": \"test@ejemplo.com\",\n  \"type\": \"alphanumeric\",\n  \"length\": 8,\n  \"expiresInSeconds\": 300\n}\n\n</code></pre>\n<h5 id=\"succesful-response\">Succesful Response</h5>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"otpId\": \"51a6fcc7-efc8-4e69-8bc1-ebb999befb2c\",\n  \"destination\": \"test@ejemplo.com\",\n  \"createdAt\": \"2025-09-10T14:05:41.5251815Z\",\n  \"expiresAt\": \"2025-09-10T14:10:41.5251815Z\"\n}\n\n</code></pre>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th><strong>Field</strong></th>\n<th><strong>Type</strong></th>\n<th><strong>Description</strong></th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>otpId</code></td>\n<td>string</td>\n<td>Unique identifier for the OTP. Use it later to validate the code.</td>\n</tr>\n<tr>\n<td><code>destination</code></td>\n<td>string</td>\n<td>Target email or phone number.</td>\n</tr>\n<tr>\n<td><code>createdAt</code></td>\n<td>datetime</td>\n<td>UTC timestamp when the OTP was created.</td>\n</tr>\n<tr>\n<td><code>expiresAt</code></td>\n<td>datetime</td>\n<td>UTC timestamp when the OTP expires.</td>\n</tr>\n</tbody>\n</table>\n</div><h5 id=\"invalid-cases-and-error-messages\">Invalid Cases and Error Messages</h5>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Case</th>\n<th>Invalid Example (JSON)</th>\n<th>Response</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>Missing</strong> <strong><code>channel</code></strong></td>\n<td><code>{\"provider\":\"generic\",\"destination\":\"test@ejemplo.com\",\"type\":\"alphanumeric\",\"length\":8,\"expiresInSeconds\":300}</code></td>\n<td><code>OTP channel is required and must be either 'Email' or 'Sms'.</code></td>\n</tr>\n<tr>\n<td><strong>Missing</strong> <strong><code>destination</code></strong></td>\n<td><code>{\"channel\":\"email\",\"provider\":\"generic\",\"type\":\"alphanumeric\",\"length\":8,\"expiresInSeconds\":300}</code></td>\n<td><code>Destination (email or phone number) is required.</code></td>\n</tr>\n<tr>\n<td><strong>Missing</strong> <strong><code>type</code></strong></td>\n<td><code>{\"channel\":\"email\",\"provider\":\"generic\",\"destination\":\"test@ejemplo.com\",\"length\":8,\"expiresInSeconds\":300}</code></td>\n<td><code>OTP type is required.</code></td>\n</tr>\n<tr>\n<td><strong>Missing</strong> <strong><code>length</code></strong></td>\n<td><code>{\"channel\":\"email\",\"provider\":\"generic\",\"destination\":\"test@ejemplo.com\",\"type\":\"alphanumeric\",\"expiresInSeconds\":300}</code></td>\n<td><code>OTP length is required.</code></td>\n</tr>\n<tr>\n<td><strong>Missing</strong> <strong><code>expiresInSeconds</code></strong></td>\n<td><code>{\"channel\":\"email\",\"provider\":\"generic\",\"destination\":\"test@ejemplo.com\",\"type\":\"alphanumeric\",\"length\":8}</code></td>\n<td><code>ExpiresInSeconds is required.</code></td>\n</tr>\n<tr>\n<td><strong>Invalid email format</strong></td>\n<td><code>{\"channel\":\"email\",\"provider\":\"generic\",\"destination\":\"testejemplo.com\",\"type\":\"alphanumeric\",\"length\":8,\"expiresInSeconds\":300}</code></td>\n<td><code>Invalid email format. It must be like name@example.com.</code></td>\n</tr>\n<tr>\n<td><strong>Invalid phone format (SMS)</strong></td>\n<td><code>{\"channel\":\"sms\",\"provider\":\"generic\",\"destination\":\"test@ejemplo.com\",\"type\":\"alphanumeric\",\"length\":8,\"expiresInSeconds\":300}</code></td>\n<td><code>Invalid phone number format. Must start with country code</code></td>\n</tr>\n<tr>\n<td><strong>Unknown/unconfigured provider</strong></td>\n<td><code>{\"channel\":\"email\",\"provider\":\"emailsenderotp\",\"destination\":\"test@ejemplo.com\",\"type\":\"alphanumeric\",\"length\":8,\"expiresInSeconds\":300}</code></td>\n<td><code>Provider not found for this client and channel.</code></td>\n</tr>\n<tr>\n<td><strong>OTP length out of range (4–12)</strong></td>\n<td><code>{\"channel\":\"email\",\"provider\":\"generic\",\"destination\":\"test@ejemplo.com\",\"type\":\"alphanumeric\",\"length\":2,\"expiresInSeconds\":300}</code></td>\n<td><code>OTP length must be between 4 and 12.</code></td>\n</tr>\n<tr>\n<td><strong>Non-positive expiration</strong></td>\n<td><code>{\"channel\":\"email\",\"provider\":\"generic\",\"destination\":\"test@ejemplo.com\",\"type\":\"alphanumeric\",\"length\":4,\"expiresInSeconds\":0}</code></td>\n<td><code>ExpiresInSeconds must be greater than zero.</code></td>\n</tr>\n</tbody>\n</table>\n</div><p><strong>Notes</strong></p>\n<ul>\n<li><p>The OTP value is never returned or stored in plaintext; only a hash with TTL is kept.</p>\n</li>\n<li><p>For <code>sms</code>, use <strong>E.164</strong> format (e.g., <code>+573001112233</code>).</p>\n</li>\n<li><p>Use <code>otpId</code> with <strong>POST /otp/validate</strong> to complete verification.</p>\n</li>\n</ul>\n<p><strong>HEADERS</strong><br /><code>x-api-key</code> — <code>{{YOUR_API_KEY}}</code><br /><code>Content-Type</code> — <code>application/json</code></p>\n<p><strong>Body</strong><br /><code>raw (json)</code> — see example above.</p>\n","urlObject":{"protocol":"https","path":["otp","generate"],"host":["cpt","cx"],"query":[],"variable":[]}},"response":[],"_postman_id":"d320468e-36db-4ee9-b929-cb91fc7e5b0c"},{"name":"Public-validate-otp","id":"e30eadc8-0d34-4cf5-8dd2-32ba80ac2597","protocolProfileBehavior":{"disableBodyPruning":true},"request":{"auth":{"type":"noauth","isInherited":false},"method":"POST","header":[{"key":"x-api-key","value":"{{YOUR_API_KEY}}","type":"text"}],"body":{"mode":"raw","raw":"\r\n{\r\n  \"otpId\": \"c4f8814c-7a9d-4e13-b7d8-d4e66d272611\",\r\n  \"otp\": \"z4dGyBPQ\"\r\n}","options":{"raw":{"language":"json"}}},"url":"https://cpt.cx/otp/validate","description":"<h4 id=\"description\"><strong>Description</strong></h4>\n<p>Validates a previously generated OTP using the <code>otpId</code> and the user-entered <code>otp</code>.</p>\n<p>On success, the OTP is <strong>consumed</strong> (one-time use) and cannot be reused.</p>\n<p><strong>Authentication</strong></p>\n<p>This endpoint requires an API Key in the request headers:</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">x-api-key: YOUR_API_KEY\nContent-Type: application/json\n\n</code></pre>\n<h5 id=\"request-body-json\">Request Body (JSON)</h5>\n<p><strong>Required fields:</strong></p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Field</th>\n<th>Type</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>otpId</code></td>\n<td>string</td>\n<td>The identifier returned by <code>/otp/generate</code>.</td>\n</tr>\n<tr>\n<td><code>otp</code></td>\n<td>string</td>\n<td>The OTP code entered by the user.</td>\n</tr>\n</tbody>\n</table>\n</div><h5 id=\"valid-request-example\">Valid Request Example</h5>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"otpId\": \"c4f8814c-7a9d-4e13-b7d8-d4e66d272611\",\n  \"otp\": \"z4dGyBPQ\"\n}\n\n</code></pre>\n<p><strong>Successful Response (200)</strong></p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n    \"success\": true,\n    \"message\": \"OTP validated successfully.\"\n}\n\n</code></pre>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th><strong>Field</strong></th>\n<th><strong>Type</strong></th>\n<th><strong>Description</strong></th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>success</code></td>\n<td>boolean</td>\n<td><code>true</code> when the OTP is valid and accepted.</td>\n</tr>\n<tr>\n<td><code>message</code></td>\n<td>string</td>\n<td>Human-readable confirmation message.</td>\n</tr>\n</tbody>\n</table>\n</div><h5 id=\"invalid-cases-and-error-messages\">Invalid Cases and Error Messages</h5>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Case</th>\n<th>Invalid Example (JSON)</th>\n<th>Response</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>Missing</strong> <strong><code>otpId</code></strong></td>\n<td><code>{\"otp\":\"n2gR\"}</code></td>\n<td><code>OtpId is required.</code></td>\n</tr>\n<tr>\n<td><strong>Missing</strong> <strong><code>otp</code></strong></td>\n<td><code>{\"otpId\":\"7dd25d97-1ae3-49a7-8aa7-54211a3fecac\"}</code></td>\n<td><code>Otp is required.</code></td>\n</tr>\n<tr>\n<td><strong>Incorrect or expired OTP</strong></td>\n<td><code>{\"otpId\":\"7dd25d97-1ae3-49a7-8aa7-54211a3fecac\",\"otp\":\"n2gR\"}</code></td>\n<td><code>Invalid OTP or expired.</code></td>\n</tr>\n<tr>\n<td><strong>Too many failed attempts (temporary lock)</strong></td>\n<td><code>{\"otpId\":\"c0d1c4ad-b46c-4e27-8731-625d381e7182\",\"otp\":\"rIayv2gN\"}</code></td>\n<td><code>Too many failed attempts. Try again later.</code></td>\n</tr>\n</tbody>\n</table>\n</div><p><strong>Behavior note:</strong><br />If the allowed attempts (configured in Compacto for the provider) are exceeded, further validations will return<br /><code>Too many failed attempts. Try again later.</code> — <strong>even if the OTP is correct</strong> — until the lock duration elapses.  </p>\n<h3 id=\"notes\">Notes</h3>\n<ul>\n<li><p>A successful validation <strong>invalidates</strong> the OTP immediately (single-use).</p>\n</li>\n<li><p>Server compares against a <strong>hash</strong> of the OTP (TTL); plaintext is never stored.</p>\n</li>\n<li><p>You do <strong>not</strong> need to send <code>destination</code>; <code>otpId</code> carries the context.</p>\n</li>\n</ul>\n","urlObject":{"protocol":"https","path":["otp","validate"],"host":["cpt","cx"],"query":[{"disabled":true,"key":"","value":null}],"variable":[]}},"response":[],"_postman_id":"e30eadc8-0d34-4cf5-8dd2-32ba80ac2597"}]}