BIO.RE
Authentication

Resend Phone OTP

Generate a fresh OTP code, reset attempts, dispatch a new SMS. Caps per challenge + cooldown between resends.

POST /api/v1/auth/resend-otp — 🌐 Public · Rate limit: 10 req / hour

Generates a fresh code for an existing Challenge, resets attempts = 0, increments resendCount, and dispatches a new SMS via the active SMS provider (admin-managed). Two server-side caps:

  • auth.otp_max_resends — per-challenge resend ceiling.
  • auth.otp_resend_cooldown_seconds — minimum seconds between resends.

Request

Body — ResendOtpDto

FieldTypeRequiredValidationNotes
challengeIdstring (UUID)IsUUID()Returned by POST /auth/send-otp

Response

200 OKApiResponseOf<ChallengeDispatchResponseDto>

{
  "success": true,
  "data": {
    "challengeId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "expiresAt": "2026-04-29T20:25:00.000Z",
    "attemptsRemaining": 5,
    "resendCount": 1
  }
}

Same shape as /send-otp but with resendCount populated.

FieldTypeNotes
challengeIdstring (UUID)Same id — Challenge row reused, code rotated
expiresAtstring (ISO 8601)New TTL window from now
attemptsRemainingnumberReset to auth.otp_max_attempts
resendCountnumberTotal dispatches so far for this Challenge

Errors

HTTPcode / i18nKeyReason
400auth.otp.resend.cap_reachedChallenge.resendCount >= auth.otp_max_resends
400auth.otp.resend.cooldownLast dispatch within auth.otp_resend_cooldown_seconds
404auth.otp.resend.not_foundchallengeId does not exist
429(throttle)Rate limit exceeded (10 req/hour)

Side effects

  1. Lookup Challenge by challengeId; verify exists, not used, not expired.
  2. Cap check: Challenge.resendCount < auth.otp_max_resends AND last dispatch ≥ auth.otp_resend_cooldown_seconds ago.
  3. Generate fresh 6-digit code; bcrypt-hash; update Challenge.codeHash.
  4. Reset Challenge.attempts = 0; increment Challenge.resendCount; refresh expiresAt.
  5. Dispatch SMS via the active SMS provider (admin-managed via external.sms.active_provider).
  6. Audit log: auth.otp.resend.success (with masked phone).

Code samples

curl -X POST https://api.bio.re/api/v1/auth/resend-otp \
  -H 'Content-Type: application/json' \
  -d '{"challengeId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"}'
async function resendOtp(challengeId: string): Promise<ChallengeDispatch> {
  const res = await fetch('https://api.bio.re/api/v1/auth/resend-otp', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ challengeId }),
  });
  const json = await res.json();
  if (!res.ok || !json.success) {
    throw Object.assign(new Error(json?.error?.message ?? 'Resend OTP failed'), {
      code: json?.error?.code,
    });
  }
  return json.data;
}
import { useMutation } from '@tanstack/react-query';

export function useResendOtp() {
  return useMutation({
    mutationFn: async (challengeId: string) => {
      const res = await fetch('/api/v1/auth/resend-otp', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ challengeId }),
      });
      const json = await res.json();
      if (!res.ok || !json.success) {
        throw Object.assign(new Error(json?.error?.message ?? 'Resend OTP failed'), {
          code: json?.error?.code,
          i18nKey: json?.error?.i18nKey,
        });
      }
      return json.data as ChallengeDispatch;
    },
  });
}

Try it

POST
/api/v1/auth/resend-otp

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

application/json

curl -X POST "https://loading/api/v1/auth/resend-otp" \  -H "Content-Type: application/json" \  -d '{    "challengeId": "007cfdcc-a46d-4340-a4c6-216ec2e4009c"  }'
{
  "success": true,
  "data": {
    "challengeId": "007cfdcc-a46d-4340-a4c6-216ec2e4009c",
    "expiresAt": "2019-08-24T14:15:22Z",
    "attemptsRemaining": 5,
    "resendCount": 0
  }
}
{
  "success": false,
  "error": {
    "code": "AUTH_UNAUTHORIZED",
    "message": "Invalid credentials",
    "i18nKey": "auth.login.invalid_credentials",
    "i18nVars": {
      "field": "email"
    },
    "details": [
      {
        "message": "email must be an email"
      }
    ],
    "correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  }
}
{
  "success": false,
  "error": {
    "code": "AUTH_UNAUTHORIZED",
    "message": "Invalid credentials",
    "i18nKey": "auth.login.invalid_credentials",
    "i18nVars": {
      "field": "email"
    },
    "details": [
      {
        "message": "email must be an email"
      }
    ],
    "correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  }
}
{
  "success": false,
  "error": {
    "code": "AUTH_UNAUTHORIZED",
    "message": "Invalid credentials",
    "i18nKey": "auth.login.invalid_credentials",
    "i18nVars": {
      "field": "email"
    },
    "details": [
      {
        "message": "email must be an email"
      }
    ],
    "correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  }
}

Source

SourcePathLines
Controllerapps/api-core/src/modules/auth/auth.controller.ts359–381 (resendOtp)
DTO (request)apps/api-core/src/modules/auth/dto/resend-otp.dto.ts4–8 (ResendOtpDto)
DTO (response)apps/api-core/src/modules/auth/dto/challenge.dto.ts7–22 (ChallengeDispatchResponseDto)
Serviceapps/api-core/src/modules/auth/challenge.service.tsresendOtp()
Prisma modelpackages/prisma/prisma/schema.prismaChallenge.resendCount, Challenge.codeHash, Challenge.attempts

On this page