Skip to content

Start signup — send a verification code to the email

POST
/auth/signup/start
curl --request POST \
--url https://api.flotera.com/api/v1/auth/signup/start \
--header 'Content-Type: application/json' \
--data '{ "email": "hello@example.com", "tenant_name": "example", "turnstile_token": "example" }'

First step of the email-verification signup flow (05 §4.3). Protected by distributed rate limits (IP prefix + hashed email) and, when configured, by a Cloudflare Turnstile challenge.

Stores a pending signup (name + email, no account yet) and emails a 6-digit code valid for 10 minutes with 5 entry attempts. Re-requesting a code for the same email is allowed once per 60 seconds; inside the cooldown the operation answers 429 with Retry-After.

Anti-enumeration (05 §8.3): the response is 202 regardless of whether the email already has an account. A taken email receives a “you already have an account” email instead of a code; the stored pending row behaves identically from the outside (including the resend cooldown), so a taken and a free email are indistinguishable to the caller. The email itself is dispatched after the response — response timing does not depend on the SMTP path either.

Operator-disabled operation. Public signup is off by default and is enabled per deployment with ERMEON_SIGNUP_ENABLED. While it is off, all three signup operations answer 404 not_found for every request, before the body is parsed and before any storage is touched. Clients must read GET /auth/config (signup_enabled) instead of probing this path.

Media typeapplication/json
object
email
required
string format: email
<= 254 characters
tenant_name
required

Display name of the tenant that will be created at the complete step; stored with the pending signup.

string
>= 1 characters <= 80 characters
turnstile_token

Cloudflare Turnstile response token from the widget. Optional in the schema because the challenge is deployment-configurable, but required in practice whenever GET /auth/config reports turnstile.enabled; omitting it then returns 403. Single-use.

string | null
<= 2048 characters
Examplegenerated
{
"email": "hello@example.com",
"tenant_name": "example",
"turnstile_token": "example"
}

Code dispatch accepted (identical for any email). The body reports the resend cooldown for the UI countdown.

Media typeapplication/json
object
resend_cooldown_seconds
required

Seconds until another code may be requested for the same email — drives the UI resend countdown.

integer
>= 1 <= 3600
Examplegenerated
{
"resend_cooldown_seconds": 1
}

Turnstile challenge missing, rejected by Cloudflare, or impossible to verify (fail closed). code=forbidden, detail="captcha verification failed". The body never depends on the submitted credentials; the token is single-use, so the widget must be reset before retrying.

Media typeapplication/problem+json

RFC 9457 problem document with a stable machine code.

object
type
required
string format: uri
title
required
string
<= 256 characters
status
required
integer
>= 100 <= 599
code
required

Stable machine-readable error code (03 §2.4).

string
Allowed values: unauthorized forbidden csrf_rejected not_found conflict idempotency_conflict validation_failed rate_limited payload_too_large unsupported_agent_version temporarily_unavailable offline_queue_full online_queue_full plan_required server_limit_reached feature_not_entitled account_in_grace account_frozen payment_pending payment_expired payment_amount_mismatch change_already_pending change_already_applied change_effective reserve_not_covered direction_changed
detail
string
<= 2048 characters
instance
string
<= 512 characters
request_id

UUID (v7 for new entities; v4 accepted during migration).

string format: uuid
errors
Array<object>
<= 100 items
object
path
required

JSON Pointer to the offending field.

string
<= 512 characters
code
required
string
<= 64 characters
Example
{
"type": "https://ermeon.com/problems/validation",
"code": "unauthorized"
}

Public signup is disabled on this deployment (code=not_found). Identical for every payload.

Media typeapplication/problem+json

RFC 9457 problem document with a stable machine code.

object
type
required
string format: uri
title
required
string
<= 256 characters
status
required
integer
>= 100 <= 599
code
required

Stable machine-readable error code (03 §2.4).

string
Allowed values: unauthorized forbidden csrf_rejected not_found conflict idempotency_conflict validation_failed rate_limited payload_too_large unsupported_agent_version temporarily_unavailable offline_queue_full online_queue_full plan_required server_limit_reached feature_not_entitled account_in_grace account_frozen payment_pending payment_expired payment_amount_mismatch change_already_pending change_already_applied change_effective reserve_not_covered direction_changed
detail
string
<= 2048 characters
instance
string
<= 512 characters
request_id

UUID (v7 for new entities; v4 accepted during migration).

string format: uuid
errors
Array<object>
<= 100 items
object
path
required

JSON Pointer to the offending field.

string
<= 512 characters
code
required
string
<= 64 characters
Example
{
"type": "https://ermeon.com/problems/validation",
"code": "unauthorized"
}

Request failed validation (code=validation_failed), with per-field errors.

Media typeapplication/problem+json

RFC 9457 problem document with a stable machine code.

object
type
required
string format: uri
title
required
string
<= 256 characters
status
required
integer
>= 100 <= 599
code
required

Stable machine-readable error code (03 §2.4).

string
Allowed values: unauthorized forbidden csrf_rejected not_found conflict idempotency_conflict validation_failed rate_limited payload_too_large unsupported_agent_version temporarily_unavailable offline_queue_full online_queue_full plan_required server_limit_reached feature_not_entitled account_in_grace account_frozen payment_pending payment_expired payment_amount_mismatch change_already_pending change_already_applied change_effective reserve_not_covered direction_changed
detail
string
<= 2048 characters
instance
string
<= 512 characters
request_id

UUID (v7 for new entities; v4 accepted during migration).

string format: uuid
errors
Array<object>
<= 100 items
object
path
required

JSON Pointer to the offending field.

string
<= 512 characters
code
required
string
<= 64 characters
Example
{
"type": "https://ermeon.com/problems/validation",
"code": "unauthorized"
}

Rate limit exceeded (code=rate_limited).

Media typeapplication/problem+json

RFC 9457 problem document with a stable machine code.

object
type
required
string format: uri
title
required
string
<= 256 characters
status
required
integer
>= 100 <= 599
code
required

Stable machine-readable error code (03 §2.4).

string
Allowed values: unauthorized forbidden csrf_rejected not_found conflict idempotency_conflict validation_failed rate_limited payload_too_large unsupported_agent_version temporarily_unavailable offline_queue_full online_queue_full plan_required server_limit_reached feature_not_entitled account_in_grace account_frozen payment_pending payment_expired payment_amount_mismatch change_already_pending change_already_applied change_effective reserve_not_covered direction_changed
detail
string
<= 2048 characters
instance
string
<= 512 characters
request_id

UUID (v7 for new entities; v4 accepted during migration).

string format: uuid
errors
Array<object>
<= 100 items
object
path
required

JSON Pointer to the offending field.

string
<= 512 characters
code
required
string
<= 64 characters
Example
{
"type": "https://ermeon.com/problems/validation",
"code": "unauthorized"
}
Retry-After
integer

Seconds to wait before retrying.

A dependency the endpoint needs is not configured or is temporarily unavailable (code=unavailable). The rest of the API keeps working — an absent external integration must not take the service down.

Media typeapplication/problem+json

RFC 9457 problem document with a stable machine code.

object
type
required
string format: uri
title
required
string
<= 256 characters
status
required
integer
>= 100 <= 599
code
required

Stable machine-readable error code (03 §2.4).

string
Allowed values: unauthorized forbidden csrf_rejected not_found conflict idempotency_conflict validation_failed rate_limited payload_too_large unsupported_agent_version temporarily_unavailable offline_queue_full online_queue_full plan_required server_limit_reached feature_not_entitled account_in_grace account_frozen payment_pending payment_expired payment_amount_mismatch change_already_pending change_already_applied change_effective reserve_not_covered direction_changed
detail
string
<= 2048 characters
instance
string
<= 512 characters
request_id

UUID (v7 for new entities; v4 accepted during migration).

string format: uuid
errors
Array<object>
<= 100 items
object
path
required

JSON Pointer to the offending field.

string
<= 512 characters
code
required
string
<= 64 characters
Example
{
"type": "https://ermeon.com/problems/validation",
"code": "unauthorized"
}