curl --request POST \
--url https://api.1-funnel.org/v1/businesses/{businessId}/otp/challenges/{challengeId}/verify \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"code": "483920"
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({code: '483920'})
};
fetch('https://api.1-funnel.org/v1/businesses/{businessId}/otp/challenges/{challengeId}/verify', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.1-funnel.org/v1/businesses/{businessId}/otp/challenges/{challengeId}/verify"
payload = { "code": "483920" }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)HttpResponse<String> response = Unirest.post("https://api.1-funnel.org/v1/businesses/{businessId}/otp/challenges/{challengeId}/verify")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"code\": \"483920\"\n}")
.asString();{
"id": "018f3c4e-7b2a-7c3d-8e9f-dddddddddddd",
"status": "verified",
"purpose": "login",
"verified_at": "2026-10-13T09:01:12.000Z"
}Verify an OTP challenge
Atomic single-use verification (ADR-020 / ADR-008 / DEC-OTP-V01). Does not reveal whether a code was “close” or expose secrets. Verification outcome is distinct from SMS delivery outcome.
curl --request POST \
--url https://api.1-funnel.org/v1/businesses/{businessId}/otp/challenges/{challengeId}/verify \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"code": "483920"
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({code: '483920'})
};
fetch('https://api.1-funnel.org/v1/businesses/{businessId}/otp/challenges/{challengeId}/verify', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.1-funnel.org/v1/businesses/{businessId}/otp/challenges/{challengeId}/verify"
payload = { "code": "483920" }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)HttpResponse<String> response = Unirest.post("https://api.1-funnel.org/v1/businesses/{businessId}/otp/challenges/{challengeId}/verify")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"code\": \"483920\"\n}")
.asString();{
"id": "018f3c4e-7b2a-7c3d-8e9f-dddddddddddd",
"status": "verified",
"purpose": "login",
"verified_at": "2026-10-13T09:01:12.000Z"
}Authorizations
OAuth 2.0 Client Credentials for the External Integration API (ADR-015, ADR-023).
Client authentication: HTTP Basic (client_secret_basic).
Access-token audience: 1funnel-external-api.
The tokenUrl below is the production target. Sandbox uses a different
host. OpenAPI cannot bind tokenUrl to the selected servers entry; see
x-1funnel-oauth on the root document and the authentication guide when published.
Coarse OAuth scopes (if any) are token-level vocabulary (ADR-013 IA-06). They
are not the ADR-022 resource permission catalogue. Resource permissions
are enforced after token validation via application-to-business grants
(ADR-016) and are documented per operation with x-required-permissions.
These URLs are architecture targets. They are not claimed to be deployed or generally available.
Headers
Request correlation identifier (STD-API-01 D12). Clients may supply a value
of 1–128 characters matching A-Za-z0-9._-. Missing or invalid values are
replaced by a server-generated identifier. Distinct from W3C traceparent,
resource ids, Idempotency-Key, and webhook event ids.
1 - 128^[A-Za-z0-9._-]+$"req_01HZX3K9EXAMPLE"
Path Parameters
Business resource selector in /v1/businesses/{businessId}/… (ADR-006).
UUIDv7-compatible opaque identifier. {businessId} is not authorization
proof. The resource server must independently verify an active
application-to-business grant, required permission, and ownership (ADR-016,
ADR-022).
UUID with version nibble 7 (UUIDv7). STD-API-01 prefers UUIDv7 for new
platform-generated external resource identifiers. Clients must still treat
the value as opaque. Not every path parameter is guaranteed to be v7 solely
because it is a UUID; use this schema where the contract asserts UUIDv7.
^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"018f3c4e-7b2a-7c3d-8e9f-0123456789ab"
OTP challenge identifier (UUIDv7).
UUID with version nibble 7 (UUIDv7). STD-API-01 prefers UUIDv7 for new
platform-generated external resource identifiers. Clients must still treat
the value as opaque. Not every path parameter is guaranteed to be v7 solely
because it is a UUID; use this schema where the contract asserts UUIDv7.
^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"018f3c4e-7b2a-7c3d-8e9f-0123456789ab"
Body
Verify an OTP challenge (single-use, atomic). Does not return or echo the code in responses.
Caller-supplied OTP code. Never logged by the API contract.
4 - 12^[0-9A-Za-z]+$Response
Challenge verified
Successful single-use verification result.
UUID with version nibble 7 (UUIDv7). STD-API-01 prefers UUIDv7 for new
platform-generated external resource identifiers. Clients must still treat
the value as opaque. Not every path parameter is guaranteed to be v7 solely
because it is a UUID; use this schema where the contract asserts UUIDv7.
^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-7[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$"018f3c4e-7b2a-7c3d-8e9f-0123456789ab"
verified Challenge purpose (lowercase snake_case public enum; ADR-020 OTP-A01). Exact allowed set is policy-controlled; values below are the initial catalogue.
login, register, password, transfer, merchant_custom "login"
RFC 3339 date-time. Response timestamps are UTC with a Z offset
(STD-API-01 D06). Client-supplied timestamps must include an explicit offset;
local times without an offset are rejected.
"2026-10-13T09:00:00.000Z"