curl --request POST \
--url 'https://api.noresponse.org/v1/accounts/acct_7f3a9c/enforcements' \
--header "Authorization: Bearer $NORESPONSE_API_KEY" \
--header 'Content-Type: application/json' \
--data '{
"risk_version": 3,
"flow": "account_creation",
"action": "restrict",
"state_before": "provisional",
"status": "failed",
"provider": "clerk",
"error_code": "clerk.rate_limited"
}'
import { NoResponse } from "@noresponse/node";
const noResponse = new NoResponse({
apiKey: process.env["NORESPONSE_API_KEY"] ?? "",
});
async function run() {
const result = await noResponse.accounts.recordEnforcement({
accountId: "acct_7f3a9c",
body: {
riskVersion: 3,
flow: "account_creation",
action: "restrict",
stateBefore: "provisional",
status: "failed",
provider: "clerk",
errorCode: "clerk.rate_limited",
},
});
console.log(result);
}
run();
from noresponse import NoResponse
import os
with NoResponse(
api_key=os.getenv("NORESPONSE_API_KEY", ""),
) as no_response:
res = no_response.accounts.record_enforcement(account_id="acct_7f3a9c", risk_version=3, flow="account_creation", action="restrict", state_before="provisional", status="failed", provider="clerk", error_code="clerk.rate_limited")
# Handle response
print(res)
package main
import(
"context"
"os"
noresponse "github.com/noresponsehq/go/v7"
"github.com/noresponsehq/go/v7/models/components"
"log"
)
func main() {
ctx := context.Background()
s := noresponse.New(
noresponse.WithSecurity(os.Getenv("NORESPONSE_API_KEY")),
)
res, err := s.Accounts.RecordEnforcement(ctx, "acct_7f3a9c", components.EnforcementRequest{
RiskVersion: 3,
Flow: components.FlowAccountCreation,
Action: components.EnforcementActionRestrict,
StateBefore: components.EnforcementStateProvisional,
Status: components.EnforcementStatusFailed,
Provider: components.EnforcementProviderClerk,
ErrorCode: noresponse.Pointer("clerk.rate_limited"),
})
if err != nil {
log.Fatal(err)
}
if res != nil {
// handle response
}
}
package hello.world;
import java.lang.Exception;
import org.noresponse.NoResponse;
import org.noresponse.models.errors.AuthenticationException;
import org.noresponse.models.errors.BadRequestException;
import org.noresponse.models.errors.ConflictException;
import org.noresponse.models.errors.NotFoundException;
import org.noresponse.models.errors.PermissionDeniedException;
import org.noresponse.models.errors.RateLimitException;
import org.noresponse.models.operations.RecordEnforcementResponse;
import org.noresponse.models.shared.EnforcementAction;
import org.noresponse.models.shared.EnforcementProvider;
import org.noresponse.models.shared.EnforcementRequest;
import org.noresponse.models.shared.EnforcementState;
import org.noresponse.models.shared.EnforcementStatus;
import org.noresponse.models.shared.Flow;
public class Application {
public static void main(String[] args) throws Exception {
NoResponse sdk = NoResponse.builder()
.apiKey(System.getenv().getOrDefault("NORESPONSE_API_KEY", ""))
.build();
RecordEnforcementResponse res = sdk.accounts().recordEnforcement()
.accountId("acct_7f3a9c")
.body(EnforcementRequest.builder()
.riskVersion(3L)
.flow(Flow.ACCOUNT_CREATION)
.action(EnforcementAction.RESTRICT)
.stateBefore(EnforcementState.PROVISIONAL)
.status(EnforcementStatus.FAILED)
.provider(EnforcementProvider.CLERK)
.errorCode("clerk.rate_limited")
.build())
.call();
if (res.enforcement().isPresent()) {
System.out.println(res.enforcement().get());
}
}
}
using NoResponse;
using NoResponse.Models.Shared;
using System;
var sdk = new NoResponseClient(apiKey: Environment.GetEnvironmentVariable("NORESPONSE_API_KEY"));
var res = await sdk.Accounts.RecordEnforcementAsync(
accountId: "acct_7f3a9c",
body: new EnforcementRequest() {
RiskVersion = 3,
Flow = Flow.AccountCreation,
Action = EnforcementAction.Restrict,
StateBefore = EnforcementState.Provisional,
Status = EnforcementStatus.Failed,
Provider = EnforcementProvider.Clerk,
ErrorCode = "clerk.rate_limited",
}
);
// handle response
<?php
declare(strict_types=1);
require 'vendor/autoload.php';
use NoResponse;
use NoResponse\Models\Shared;
$sdk = NoResponse\NoResponseClient::builder()
->setSecurity(
getenv('NORESPONSE_API_KEY') ?: ''
)
->build();
$body = new Shared\EnforcementRequest(
riskVersion: 3,
flow: Shared\Flow::AccountCreation,
action: Shared\EnforcementAction::Restrict,
stateBefore: Shared\EnforcementState::Provisional,
status: Shared\EnforcementStatus::Failed,
provider: Shared\EnforcementProvider::Clerk,
errorCode: 'clerk.rate_limited',
);
$response = $sdk->accounts->recordEnforcement(
accountId: 'acct_7f3a9c',
body: $body
);
if ($response->enforcement !== null) {
// handle response
}
require 'noresponse'
Models = ::NoResponse::Models
s = ::NoResponse::NoResponseClient.new(
api_key: ENV.fetch('NORESPONSE_API_KEY', '')
)
res = s.accounts.record_enforcement(account_id: 'acct_7f3a9c', body: Models::Shared::EnforcementRequest.new(
risk_version: 3,
flow: Models::Shared::Flow::ACCOUNT_CREATION,
action: Models::Shared::EnforcementAction::RESTRICT,
state_before: Models::Shared::EnforcementState::PROVISIONAL,
status: Models::Shared::EnforcementStatus::FAILED,
provider: Models::Shared::EnforcementProvider::CLERK,
error_code: 'clerk.rate_limited'
))
unless res.nil?
# handle response
end
{
"id": "enf_01JA2ZF5N6P7Q8R9S0T1V2W3X4",
"account_id": "acct_7f3a9c",
"risk_version": 3,
"flow": "account_creation",
"action": "restrict",
"state_before": "provisional",
"status": "failed",
"provider": "clerk",
"error_code": "clerk.rate_limited",
"created_at": "2026-10-14T09:00:02.000Z"
}{
"id": "enf_01JA2ZF5N6P7Q8R9S0T1V2W3X4",
"account_id": "acct_7f3a9c",
"risk_version": 3,
"flow": "account_creation",
"action": "restrict",
"state_before": "provisional",
"status": "applied",
"provider": "clerk",
"created_at": "2026-10-14T09:00:02.000Z"
}{
"type": "<string>",
"title": "<string>",
"status": 400,
"detail": "<string>",
"code": "invalid_request",
"request_id": "<string>",
"instance": "<string>",
"invalid_params": [
{
"field": "<string>",
"reason": "<string>",
"example": "<string>"
}
]
}{
"type": "<string>",
"title": "<string>",
"status": 401,
"detail": "<string>",
"code": "unauthorized",
"request_id": "<string>",
"instance": "<string>",
"invalid_params": [
{
"field": "<string>",
"reason": "<string>",
"example": "<string>"
}
]
}{
"type": "https://docs.noresponse.org/problems/workspace-forbidden",
"title": "Forbidden",
"status": 403,
"detail": "The API key does not carry a scope this operation accepts.",
"code": "workspace_forbidden",
"request_id": "0123456789abcdef0123456789abcdef"
}{
"type": "<string>",
"title": "<string>",
"status": 404,
"detail": "<string>",
"code": "not_found",
"request_id": "<string>",
"instance": "<string>",
"invalid_params": [
{
"field": "<string>",
"reason": "<string>",
"example": "<string>"
}
]
}{
"type": "https://docs.noresponse.org/problems/risk-version-ahead",
"title": "Risk version ahead",
"status": 409,
"detail": "risk_version is newer than the account's current risk_version.",
"code": "risk_version_ahead",
"request_id": "0123456789abcdef0123456789abcdef"
}{
"type": "<string>",
"title": "<string>",
"status": 413,
"detail": "<string>",
"code": "payload_too_large",
"request_id": "<string>",
"instance": "<string>",
"invalid_params": [
{
"field": "<string>",
"reason": "<string>",
"example": "<string>"
}
]
}{
"type": "<string>",
"title": "<string>",
"status": 415,
"detail": "<string>",
"code": "unsupported_media_type",
"request_id": "<string>",
"instance": "<string>",
"invalid_params": [
{
"field": "<string>",
"reason": "<string>",
"example": "<string>"
}
]
}{
"type": "https://docs.noresponse.org/problems/rate-limited",
"title": "Rate limited",
"status": 429,
"detail": "The tenant's assessment rate is exhausted; retry after the Retry-After interval.",
"code": "rate_limited",
"request_id": "0123456789abcdef0123456789abcdef"
}{
"type": "<string>",
"title": "<string>",
"status": 503,
"detail": "<string>",
"code": "service_unavailable",
"request_id": "<string>",
"instance": "<string>",
"invalid_params": [
{
"field": "<string>",
"reason": "<string>",
"example": "<string>"
}
]
}Record an enforcement
Records what you did to an account in your identity provider: the action (or none) for the decision of one flow at one risk_version, the account’s state_before, the provider, and whether the action was applied or failed. An enforcement is your action, not an outcome. It does not change the account’s risk. To report what you learned about an assessment, use POST /v1/assessments/{assessment_id}/outcomes.
NoResponse keeps one record for each account, risk_version, flow and action. The first report answers 201. A repeat answers 200 with the stored record and changes nothing, so you can retry without an Idempotency-Key. One exception: an applied report after a failed record makes the record applied, keeps its id and created_at, removes error_code and answers 201.
A risk_version older than the account’s current one is accepted and stored. Only an applied record at the current risk_version removes the account from GET /v1/accounts?unenforced=true. A risk_version newer than the current one answers 409 risk_version_ahead. An account with no current risk answers 404 not_found.
Send error_code only with status: "failed". Requires the history scope.
curl --request POST \
--url 'https://api.noresponse.org/v1/accounts/acct_7f3a9c/enforcements' \
--header "Authorization: Bearer $NORESPONSE_API_KEY" \
--header 'Content-Type: application/json' \
--data '{
"risk_version": 3,
"flow": "account_creation",
"action": "restrict",
"state_before": "provisional",
"status": "failed",
"provider": "clerk",
"error_code": "clerk.rate_limited"
}'
import { NoResponse } from "@noresponse/node";
const noResponse = new NoResponse({
apiKey: process.env["NORESPONSE_API_KEY"] ?? "",
});
async function run() {
const result = await noResponse.accounts.recordEnforcement({
accountId: "acct_7f3a9c",
body: {
riskVersion: 3,
flow: "account_creation",
action: "restrict",
stateBefore: "provisional",
status: "failed",
provider: "clerk",
errorCode: "clerk.rate_limited",
},
});
console.log(result);
}
run();
from noresponse import NoResponse
import os
with NoResponse(
api_key=os.getenv("NORESPONSE_API_KEY", ""),
) as no_response:
res = no_response.accounts.record_enforcement(account_id="acct_7f3a9c", risk_version=3, flow="account_creation", action="restrict", state_before="provisional", status="failed", provider="clerk", error_code="clerk.rate_limited")
# Handle response
print(res)
package main
import(
"context"
"os"
noresponse "github.com/noresponsehq/go/v7"
"github.com/noresponsehq/go/v7/models/components"
"log"
)
func main() {
ctx := context.Background()
s := noresponse.New(
noresponse.WithSecurity(os.Getenv("NORESPONSE_API_KEY")),
)
res, err := s.Accounts.RecordEnforcement(ctx, "acct_7f3a9c", components.EnforcementRequest{
RiskVersion: 3,
Flow: components.FlowAccountCreation,
Action: components.EnforcementActionRestrict,
StateBefore: components.EnforcementStateProvisional,
Status: components.EnforcementStatusFailed,
Provider: components.EnforcementProviderClerk,
ErrorCode: noresponse.Pointer("clerk.rate_limited"),
})
if err != nil {
log.Fatal(err)
}
if res != nil {
// handle response
}
}
package hello.world;
import java.lang.Exception;
import org.noresponse.NoResponse;
import org.noresponse.models.errors.AuthenticationException;
import org.noresponse.models.errors.BadRequestException;
import org.noresponse.models.errors.ConflictException;
import org.noresponse.models.errors.NotFoundException;
import org.noresponse.models.errors.PermissionDeniedException;
import org.noresponse.models.errors.RateLimitException;
import org.noresponse.models.operations.RecordEnforcementResponse;
import org.noresponse.models.shared.EnforcementAction;
import org.noresponse.models.shared.EnforcementProvider;
import org.noresponse.models.shared.EnforcementRequest;
import org.noresponse.models.shared.EnforcementState;
import org.noresponse.models.shared.EnforcementStatus;
import org.noresponse.models.shared.Flow;
public class Application {
public static void main(String[] args) throws Exception {
NoResponse sdk = NoResponse.builder()
.apiKey(System.getenv().getOrDefault("NORESPONSE_API_KEY", ""))
.build();
RecordEnforcementResponse res = sdk.accounts().recordEnforcement()
.accountId("acct_7f3a9c")
.body(EnforcementRequest.builder()
.riskVersion(3L)
.flow(Flow.ACCOUNT_CREATION)
.action(EnforcementAction.RESTRICT)
.stateBefore(EnforcementState.PROVISIONAL)
.status(EnforcementStatus.FAILED)
.provider(EnforcementProvider.CLERK)
.errorCode("clerk.rate_limited")
.build())
.call();
if (res.enforcement().isPresent()) {
System.out.println(res.enforcement().get());
}
}
}
using NoResponse;
using NoResponse.Models.Shared;
using System;
var sdk = new NoResponseClient(apiKey: Environment.GetEnvironmentVariable("NORESPONSE_API_KEY"));
var res = await sdk.Accounts.RecordEnforcementAsync(
accountId: "acct_7f3a9c",
body: new EnforcementRequest() {
RiskVersion = 3,
Flow = Flow.AccountCreation,
Action = EnforcementAction.Restrict,
StateBefore = EnforcementState.Provisional,
Status = EnforcementStatus.Failed,
Provider = EnforcementProvider.Clerk,
ErrorCode = "clerk.rate_limited",
}
);
// handle response
<?php
declare(strict_types=1);
require 'vendor/autoload.php';
use NoResponse;
use NoResponse\Models\Shared;
$sdk = NoResponse\NoResponseClient::builder()
->setSecurity(
getenv('NORESPONSE_API_KEY') ?: ''
)
->build();
$body = new Shared\EnforcementRequest(
riskVersion: 3,
flow: Shared\Flow::AccountCreation,
action: Shared\EnforcementAction::Restrict,
stateBefore: Shared\EnforcementState::Provisional,
status: Shared\EnforcementStatus::Failed,
provider: Shared\EnforcementProvider::Clerk,
errorCode: 'clerk.rate_limited',
);
$response = $sdk->accounts->recordEnforcement(
accountId: 'acct_7f3a9c',
body: $body
);
if ($response->enforcement !== null) {
// handle response
}
require 'noresponse'
Models = ::NoResponse::Models
s = ::NoResponse::NoResponseClient.new(
api_key: ENV.fetch('NORESPONSE_API_KEY', '')
)
res = s.accounts.record_enforcement(account_id: 'acct_7f3a9c', body: Models::Shared::EnforcementRequest.new(
risk_version: 3,
flow: Models::Shared::Flow::ACCOUNT_CREATION,
action: Models::Shared::EnforcementAction::RESTRICT,
state_before: Models::Shared::EnforcementState::PROVISIONAL,
status: Models::Shared::EnforcementStatus::FAILED,
provider: Models::Shared::EnforcementProvider::CLERK,
error_code: 'clerk.rate_limited'
))
unless res.nil?
# handle response
end
{
"id": "enf_01JA2ZF5N6P7Q8R9S0T1V2W3X4",
"account_id": "acct_7f3a9c",
"risk_version": 3,
"flow": "account_creation",
"action": "restrict",
"state_before": "provisional",
"status": "failed",
"provider": "clerk",
"error_code": "clerk.rate_limited",
"created_at": "2026-10-14T09:00:02.000Z"
}{
"id": "enf_01JA2ZF5N6P7Q8R9S0T1V2W3X4",
"account_id": "acct_7f3a9c",
"risk_version": 3,
"flow": "account_creation",
"action": "restrict",
"state_before": "provisional",
"status": "applied",
"provider": "clerk",
"created_at": "2026-10-14T09:00:02.000Z"
}{
"type": "<string>",
"title": "<string>",
"status": 400,
"detail": "<string>",
"code": "invalid_request",
"request_id": "<string>",
"instance": "<string>",
"invalid_params": [
{
"field": "<string>",
"reason": "<string>",
"example": "<string>"
}
]
}{
"type": "<string>",
"title": "<string>",
"status": 401,
"detail": "<string>",
"code": "unauthorized",
"request_id": "<string>",
"instance": "<string>",
"invalid_params": [
{
"field": "<string>",
"reason": "<string>",
"example": "<string>"
}
]
}{
"type": "https://docs.noresponse.org/problems/workspace-forbidden",
"title": "Forbidden",
"status": 403,
"detail": "The API key does not carry a scope this operation accepts.",
"code": "workspace_forbidden",
"request_id": "0123456789abcdef0123456789abcdef"
}{
"type": "<string>",
"title": "<string>",
"status": 404,
"detail": "<string>",
"code": "not_found",
"request_id": "<string>",
"instance": "<string>",
"invalid_params": [
{
"field": "<string>",
"reason": "<string>",
"example": "<string>"
}
]
}{
"type": "https://docs.noresponse.org/problems/risk-version-ahead",
"title": "Risk version ahead",
"status": 409,
"detail": "risk_version is newer than the account's current risk_version.",
"code": "risk_version_ahead",
"request_id": "0123456789abcdef0123456789abcdef"
}{
"type": "<string>",
"title": "<string>",
"status": 413,
"detail": "<string>",
"code": "payload_too_large",
"request_id": "<string>",
"instance": "<string>",
"invalid_params": [
{
"field": "<string>",
"reason": "<string>",
"example": "<string>"
}
]
}{
"type": "<string>",
"title": "<string>",
"status": 415,
"detail": "<string>",
"code": "unsupported_media_type",
"request_id": "<string>",
"instance": "<string>",
"invalid_params": [
{
"field": "<string>",
"reason": "<string>",
"example": "<string>"
}
]
}{
"type": "https://docs.noresponse.org/problems/rate-limited",
"title": "Rate limited",
"status": 429,
"detail": "The tenant's assessment rate is exhausted; retry after the Retry-After interval.",
"code": "rate_limited",
"request_id": "0123456789abcdef0123456789abcdef"
}{
"type": "<string>",
"title": "<string>",
"status": 503,
"detail": "<string>",
"code": "service_unavailable",
"request_id": "<string>",
"instance": "<string>",
"invalid_params": [
{
"field": "<string>",
"reason": "<string>",
"example": "<string>"
}
]
}Authorizations
Your API key, sent as Authorization: Bearer <key>. A key is nrk_live_ or nrk_test_, a 26-character key id, _ and a 43-character secret. A key sees only its own environment's data.
Each key has one or more scopes: assess creates and reads assessments, records outcomes and reports key usage; history reads assessments, an account's current risk, the accounts whose risk changed and an account's history, records enforcements and erases an account; detailed adds the detailed view; webhooks registers, tests, rotates and deletes webhook endpoints; integrations registers and manages hosted provider integrations. A missing, unknown, revoked or expired key answers 401. A valid key without the scope an operation needs answers 403.
Path Parameters
The account_id the workspace sends with its assessments, as one path segment (percent-encode '|' and ':' if the client requires it). A value that does not match the account_id shape answers 400 invalid_request. The provider's own opaque identifier for the account being checked, scoped to the calling workspace. It must be provider-opaque and must not contain a direct identifier (email, phone, name, national id): that is a provider obligation the schema can only partly check. '|' is accepted so an Auth0 user id such as 'auth0|64abc' passes unchanged; '@', '+', '$' and whitespace are refused by the pattern, as is an exact-shape JWT; a hex digest such as a SHA-256 is allowed, because hashing an internal id is a common pseudonymous scheme. A value such as '07700900123' or 'jane.doe.gmail.com' cannot be told apart from an opaque id. It travels in bodies, and in a URL only as the path segment of GET /v1/accounts/{account_id} and its /assessments. An assessment requested by a provider hook at pre_registration, which has no account yet, carries the server-issued account_id pre: followed by its assessment_id.
1 - 128^[A-Za-z0-9][A-Za-z0-9._:|-]{0,127}$Body
POST /v1/accounts/{account_id}/enforcements body: what the workspace did to the account in its identity provider after reading the account's risk at risk_version. The account is named by the path and the workspace by the API key, so neither is a body member. An enforcement is the workspace's own action, not an outcome: what the workspace later learned about an assessment goes to POST /v1/assessments/{assessment_id}/outcomes. The record's key is (account, risk_version, flow, action), so a retry of the same report is safe and needs no Idempotency-Key.
The account's risk_version the workspace acted on, as read from GET /v1/accounts/{account_id}, GET /v1/accounts, an assessment answer or account.risk_changed. A version older than the account's current one is accepted and stored; a version newer than the current one answers 409 risk_version_ahead.
x >= 1The flow whose decision (decisions_by_flow) the workspace acted on.
account_creation, login, purchase What the workspace did in its identity provider: revoke_sessions (signed the account out everywhere), restrict (limited what the account can do), unrestrict (lifted an earlier restriction), fail_onboarding (stopped an account that was still onboarding), reject_activation (refused to activate a provisional account) or none (decided to take no action).
revoke_sessions, restrict, unrestrict, fail_onboarding, reject_activation, none The account's state in the workspace's identity provider just before the action: onboarding, provisional (created, not yet activated), active, restricted or rejected.
onboarding, provisional, active, restricted, rejected applied when the identity provider accepted the action, failed when it refused it or could not be reached. Only applied counts as enforced.
applied, failed The identity provider the action ran in; custom for the workspace's own user store.
clerk, workos, auth0, custom Why a failed action failed, as a short code of the workspace's choosing (lower-case letters, digits, '_', '.' and '-'), such as the identity provider's error code. Allowed only with status failed: with status applied it answers 400 invalid_request. Keep personal data out of it.
1 - 128^[a-z0-9_.-]+$Response
The enforcement was already recorded: the record as first stored. The report did not change it.
The answer of POST /v1/accounts/{account_id}/enforcements: one enforcement of one of the calling workspace's accounts, kept once per (account, risk_version, flow, action). The first report answers 201 with the new record. A repeat answers 200 with the record as first stored, whatever the repeat's state_before, status, provider or error_code. The one exception is a stored failed record followed by an applied report: the record becomes applied, keeps its id and created_at, takes the applied report's state_before and provider, drops error_code and answers 201, so a successful retry after a failure is recorded. Only an applied record at the account's current risk_version takes the account out of GET /v1/accounts?unenforced=true.
Opaque, server-assigned id of one recorded enforcement: 'enf_' and 26 Crockford base32 characters. Unique within the workspace.
^enf_[0-9A-HJKMNP-TV-Z]{26}$The account_id of the request path.
1 - 128^[A-Za-z0-9][A-Za-z0-9._:|-]{0,127}$The account's risk_version the workspace acted on. It can be older than the account's current risk_version: such a record is kept and does not count for unenforced.
x >= 1The flow whose decision the workspace acted on.
account_creation, login, purchase What the workspace did in its identity provider: revoke_sessions (signed the account out everywhere), restrict (limited what the account can do), unrestrict (lifted an earlier restriction), fail_onboarding (stopped an account that was still onboarding), reject_activation (refused to activate a provisional account) or none (decided to take no action).
revoke_sessions, restrict, unrestrict, fail_onboarding, reject_activation, none The account's state in the workspace's identity provider just before the action: onboarding, provisional (created, not yet activated), active, restricted or rejected.
onboarding, provisional, active, restricted, rejected applied when the identity provider accepted the action, failed when it refused it or could not be reached. Only applied counts as enforced.
applied, failed The identity provider the action ran in; custom for the workspace's own user store.
clerk, workos, auth0, custom When NoResponse first recorded this enforcement, in UTC. A failed record that becomes applied keeps it.
Why a failed action failed, as a short code of the workspace's choosing (lower-case letters, digits, '_', '.' and '-'), such as the identity provider's error code. Allowed only with status failed: with status applied it answers 400 invalid_request. Keep personal data out of it.
1 - 128^[a-z0-9_.-]+$