CRYTIFY API

Developer Docs

Crytify exposes two MVP endpoints: IP intelligence for raw network context and request trust for ALLOW, CHALLENGE, or BLOCK decisions.

First time here?

Step-by-step guide — from sign-up to live

A walkthrough for every platform (PHP, WordPress, Python, Node.js).

Open Guide →

Quick start

  1. Create an account at https://dashboard.crytify.com and open the dashboard.
  2. Go to Dashboard > Integration and create an API key.
  3. Open Dashboard > Protection and choose Easy, Medium, or Hard.
  4. Download crytify.php and require it before rendering protected pages.
  5. Let Crytify handle ALLOW, BLOCK, and hosted CHALLENGE redirects.

ONE-FILE PHP INSTALL

Protect any PHP page with one require.

Download the Crytify drop-in file, place it beside your protected page, then call crytify_gate() before any HTML output.

Download crytify.php

1. Download

curl -o crytify.php "https://crytify.com/download/crytify.php"

2. Add to index.php

<?php
require_once __DIR__ . '/crytify.php';

crytify_gate('cry_live_xxxxxxxxxxxx');

// Your existing page starts here.
?>
If Crytify returns CHALLENGE, the file redirects the visitor to Crytify's hosted challenge page automatically. After verification, the visitor returns to the original page and the file sends the challenge token back to Crytify. In production, you can omit the key argument and set CRYTIFY_API_KEY in server env instead.

CUSTOM CHALLENGE

Brand the hosted challenge from the dashboard.

In Dashboard > Protection, choose Custom HTML if you want the challenge page to match the client brand. Crytify still owns the token, CSRF, expiry, submit action, and redirect flow.

Required placeholders

{{challenge_action}} Required form action.
{{csrf_field}} Required hidden CSRF field.
{{challenge_submit_button}} Required safe Continue button.
{{challenge_message}} Recommended visitor-facing message.
{{challenge_error}} Recommended error display.

Minimal valid HTML

<div class="challenge-box">
  <h1>Verify before continuing</h1>
  <p>{{challenge_message}}</p>
  {{challenge_error}}
  <form method="post" action="{{challenge_action}}">
    {{csrf_field}}
    {{challenge_submit_button}}
  </form>
</div>
POST /v1/ip

IP Intelligence

Returns normalized network intelligence. This endpoint never returns ALLOW, CHALLENGE, or BLOCK.

{
  "ip": "8.8.8.8"
}
POST /v1/trust

Trust Decision

Evaluates request context and returns a synchronous trust decision with broad public reason categories.

{
  "ip": "203.0.113.5",
  "method": "POST",
  "path": "/login",
  "mode": "medium",
  "headers": {
    "user-agent": "Mozilla/5.0 ...",
    "accept": "text/html"
  }
}

Authentication

Send your API key with each request. Invalid or unauthorized keys are never charged.

Credits

IP lookup costs 1 credit. Trust decisions cost 2, 4, or 8 credits for easy, medium, or hard mode.

Sanitization

Sensitive headers such as cookies, authorization, tokens, secrets, and passwords are not stored raw.

ERROR SYMPTOM INDEX

Middleware and pre-render security fixes

Developers often search raw middleware failures, status codes, and pre-render symptoms. These notes map common infrastructure symptoms to safe request trust patterns.

Next.js middleware timeout before render

Middleware waits on a remote trust check and the protected route renders slowly or times out.

Move the trust call to a fast server-side gate, enforce a short timeout, and fail open or fail closed based on page sensitivity. Crytify returns a compact ALLOW, CHALLENGE, or BLOCK decision for pre-render enforcement.

  1. Read the client IP and sanitized request headers in middleware or route handler code.
  2. Call POST /v1/trust with the route path and selected protection mode.
  3. Use a short timeout and return a local fallback when the network is unavailable.
  4. Apply the decision before rendering protected content.

403 Forbidden pre-render bypass

A route returns 403 inconsistently, or protected content flashes before the challenge decision is applied.

Run the trust decision before the page body is generated. For client-side gates, hide protected content until the server confirms ALLOW or CHALLENGE.

  1. Move trust enforcement before HTML generation or initial content reveal.
  2. Avoid relying on client-only checks for sensitive pages.
  3. Return BLOCK as 403, CHALLENGE as a verification step, and ALLOW as normal rendering.
  4. Log the request ID for dashboard investigation.

Express rate limit false positive login

Legitimate users are blocked because IP-only rate limits treat shared networks as one actor.

Combine rate limits with a request trust decision instead of blocking solely by IP count. Crytify adds context and returns a decision your app can enforce.

  1. Keep coarse rate limits for obvious floods.
  2. Send high-risk login attempts to /v1/trust with medium or hard mode.
  3. Challenge uncertain requests instead of blocking every shared-network user.
  4. Review broad reason categories in the dashboard.

Cloudflare Worker fetch timeout auth gate

An edge worker fetches an external API and delays authentication or checkout routes.

Use a small request body, colocate the trust call near the edge where possible, and enforce a strict timeout strategy. Crytify responses are compact for edge middleware.

  1. Send only required request context to /v1/trust.
  2. Use AbortController to cap latency.
  3. Cache static assets separately from protected routes.
  4. Fail according to endpoint sensitivity when the trust API is unavailable.

Laravel middleware returns 419 or 403 during bot protection

Security middleware conflicts with CSRF/session handling or blocks before the app can render an error page.

Separate CSRF/session errors from request trust decisions. Run Crytify as a pre-render gate and map ALLOW, CHALLENGE, and BLOCK to predictable app responses.

  1. Validate session and CSRF separately from trust checks.
  2. Call /v1/trust only after the request is structurally valid.
  3. Return a verification page for CHALLENGE and 403 for BLOCK.
  4. Do not expose internal evidence details in public errors.

nginx auth_request 403 loop

A reverse proxy or auth subrequest repeatedly redirects or returns 403 for protected paths.

Keep the edge auth decision stateless, exempt the challenge route from the protected location, and pass only normalized context to the trust API.

  1. Exclude challenge and static asset routes from the protected gate.
  2. Forward normalized IP and path metadata to the application gate.
  3. Apply Crytify decisions once per protected request.
  4. Record the request ID for debugging.

Base URL

https://api.crytify.com

Authentication

Send your API key on every API request using the Bearer token format. This works the same way regardless of HTTP method — /v1/ip and /v1/trust are POST, while /v1/usage and /v1/me are GET.

curl -X POST "https://api.crytify.com/v1/trust" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer cry_live_xxxxxxxxxxxx" \
  -d '{ ... }'

IP Lookup

Use /v1/ip when you only need network intelligence. It costs 1 credit and does not return a trust decision.

curl -X POST "https://api.crytify.com/v1/ip" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer cry_live_xxxxxxxxxxxx" \
  -d '{
    "ip": "8.8.8.8"
  }'
{
  "request_id": "req_123",
  "ip": "8.8.8.8",
  "network": {
    "country": "US",
    "network_type": "business"
  },
  "credits": {
    "charged": 1,
    "remaining_today": 999
  }
}

BIN Lookup

Free · No API key required

Look up card network, type, issuing bank, and country from the first 6–8 digits of any payment card (BIN/IIN). No authentication needed — rate limited to 10 requests per minute per IP.

curl "https://api.crytify.com/api/bin/453201"
{
  "bin": "453201",
  "scheme": "Visa",
  "type": "Debit",
  "brand": "Visa Classic",
  "prepaid": false,
  "bank": {
    "name": "Jyske Bank",
    "city": "Silkeborg",
    "phone": "+4589893300"
  },
  "country": {
    "name": "Denmark",
    "code": "DK",
    "emoji": "🇩🇰",
    "currency": "DKK"
  }
}
FieldDescription
bin The BIN you queried (6–8 digits).
scheme Card network: Visa, Mastercard, Amex, etc.
type Credit, Debit, or Prepaid.
brand Sub-brand (e.g. Visa Classic, Mastercard Gold).
prepaid true if the card is prepaid.
bank.name Issuing bank name.
country.code ISO 3166-1 alpha-2 country code.
country.currency ISO 4217 currency code.

Returns HTTP 404 if BIN is not in the database. Returns HTTP 429 when rate limit is exceeded — back off and retry after 60 seconds.

Trust Decision

Use /v1/trust before rendering protected content. Send IP, method, path, and sanitized browser headers. Optionally include a fingerprint object from the JavaScript SDK to improve request consistency analysis.

curl -X POST "https://api.crytify.com/v1/trust" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer cry_live_xxxxxxxxxxxx" \
  -d '{
    "ip": "203.0.113.5",
    "mode": "hard",
    "method": "POST",
    "path": "/login",
    "url": "https://app.example.com/login",
    "challenge_return_url": "https://app.example.com/login",
    "headers": {
      "user-agent": "Mozilla/5.0 ...",
      "accept": "text/html,application/xhtml+xml",
      "accept-language": "en-US,en;q=0.9",
      "sec-ch-ua-platform": "\"Windows\""
    },
    "fingerprint": {
      "client_context": "generated by the Crytify JavaScript SDK"
    }
  }'
{
  "request_id": "req_456",
  "decision": "CHALLENGE",
  "mode": "hard",
  "trust_score": 42,
  "risk_score": 61,
  "reasons": [
    "identity_risk"
  ],
  "challenge": {
    "type": "hosted",
    "url": "https://crytify.com/challenge/abc123",
    "expires_in": 600,
    "token_param": "crytify_challenge_token"
  },
  "network": {
    "country": "US",
    "network_type": "unknown"
  },
  "credits": {
    "charged": 8,
    "remaining_today": 992
  }
}

Hosted challenge flow

When the decision is CHALLENGE, redirect the visitor to challenge.url. After verification, Crytify redirects back to challenge_return_url with crytify_challenge_token. Send that value as challenge_token in the next /v1/trust call.

Usage

GET · Free — does not consume credits

Check remaining daily credits and plan limits for the authenticated key's account. Useful for showing usage in your own dashboard or alerting before you run out.

curl "https://api.crytify.com/v1/usage" \
  -H "Authorization: Bearer cry_live_xxxxxxxxxxxx"
{
  "user_id": 7,
  "plan": "free",
  "credits": {
    "daily_limit": 1000,
    "daily_used": 42,
    "daily_remaining": 958,
    "unlimited": false,
    "balance": 0,
    "reset_at": "2026-07-04T00:00:00Z"
  }
}

Me

GET · Free — does not consume credits

Returns metadata about the API key being used and the account's current plan. Useful for verifying which key/mode is active without checking the dashboard.

curl "https://api.crytify.com/v1/me" \
  -H "Authorization: Bearer cry_live_xxxxxxxxxxxx"
{
  "api_key": {
    "id": 22,
    "name": "Default Key",
    "prefix": "cry_live_b6ba5ef81c422782",
    "default_mode": "medium",
    "can_override_mode": false,
    "status": "active",
    "created_at": "2026-07-03 01:05:42"
  },
  "account": {
    "id": 7,
    "plan": "free"
  }
}

Modes and Credits

Mode Credits Use case
easy2Low-friction content pages.
medium4Default mode for forms, account pages, and general protection.
hard8Login, signup, checkout, admin, and API protection.

Recommended Headers

Send useful browser headers, but do not send raw cookies, authorization tokens, passwords, or secrets.

user-agent
accept
accept-language
accept-encoding
sec-fetch-site
sec-fetch-mode
sec-fetch-dest
sec-ch-ua
sec-ch-ua-mobile
sec-ch-ua-platform

Error Codes

CodeHTTPMeaning
invalid_json 400 Request body is not valid JSON.
missing_required_field 400 A required field such as ip is missing.
invalid_ip 400 The submitted IP address is invalid.
invalid_api_key 401 API key is missing or invalid.
insufficient_credits 402 Wallet does not have enough credits.
rate_limited 429 Too many requests.
internal_error 500 Internal error. Charged credits are refunded.

Enforcement Example

Your application decides what to do with the returned decision. A typical pre-render gate looks like this:

if ($decision === 'BLOCK') {
    http_response_code(403);
    exit('Blocked');
}

if ($decision === 'CHALLENGE') {
    header('Location: ' . $result['challenge']['url']);
    exit;
}

if (!empty($_GET['crytify_challenge_token'])) {
    // Send this token in the next /v1/trust call as challenge_token.
}

renderProtectedPage();

Page Protection

SDK · Two-layer gate

The Crytify SDK lets you hide page content from bots before it is ever rendered or visible. There are two complementary layers — use them together for maximum coverage.

1

Server-side gate (PHP)

Evaluate the visitor IP and headers in your PHP code before rendering anything. Blocks all bots — including those that don't execute JavaScript (curl, Python requests, Scrapy, wget).

2

Client-side gate (JS)

A script in <head> hides the page immediately, collects consistency metadata, and only reveals content after the trust check passes.

BrowserYour serverCrytify /v1/trust
               ↓ PHP gate (before render)

Browsercrytify-protect.jsYour endpointCrytify /v1/trust
                                  ↓ JS gate (before visible)

JavaScript SDK

crytify-protect.js is a drop-in script that hides your page immediately on load, collects a browser fingerprint, and sends it to your backend. Content is only shown after your backend confirms the visitor is allowed.

1. Embed in <head> — before any other scripts

<head>
  <!-- Must be the first script in <head> -->
  <script src="/crytify-protect.js"
          data-endpoint="/api/crytify-check"
          data-block-url="/blocked"
          data-timeout="5000">
  </script>
</head>

Options

Attribute Required Description
data-endpoint Yes URL of your backend trust-check endpoint. The JS will POST the fingerprint here.
data-block-url No Redirect here when the visitor is blocked. Defaults to an inline block message.
data-timeout No Milliseconds before failing open (showing content anyway) if your endpoint doesn't respond. Default: 5000.
data-debug No Set to "true" to print decision logs to the browser console.

2. Create the backend endpoint (data-endpoint)

The JS POSTs { "fingerprint": { ... } } to your endpoint. Your endpoint calls Crytify and returns { "allow": true } or { "allow": false }.

<?php
// /api/crytify-check.php — the endpoint crytify-protect.js calls

require 'CrytifyClient.php';

header('Content-Type: application/json');

$body        = json_decode(file_get_contents('php://input'), true);
$fingerprint = $body['fingerprint'] ?? null;

$crytify = new CrytifyClient('cry_live_xxxxxxxxxxxx');
$result  = $crytify->trust(
    CrytifyClient::resolveIp(),
    getallheaders(),
    ['fingerprint' => $fingerprint]
);

echo json_encode(['allow' => $result->isAllow(), 'reason' => $result->reason()]);
Fail-open safety: If your endpoint is unreachable or takes longer than data-timeout milliseconds, the script automatically reveals the page. This prevents a Crytify outage from breaking your site for real users.

PHP SDK

CrytifyClient.php is a self-contained PHP class with no dependencies. Drop it anywhere in your project. Use it as middleware to gate page rendering before a single byte of HTML is sent.

Download

# Copy the file into your project
cp crytify-sdk/CrytifyClient.php your-project/lib/CrytifyClient.php

Basic usage — server-side gate

Place this at the very top of any PHP page you want to protect, before any output.

<?php
require 'CrytifyClient.php';

$crytify = new CrytifyClient('cry_live_xxxxxxxxxxxx', mode: 'medium');

$result = $crytify->trust(
    CrytifyClient::resolveIp(),   // resolves CF-Connecting-IP / X-Forwarded-For automatically
    getallheaders(),
    ['path' => $_SERVER['REQUEST_URI'], 'method' => $_SERVER['REQUEST_METHOD']]
);

if ($result->isBlock()) {
    http_response_code(403);
    include 'blocked.html';
    exit;
}

if ($result->isChallenge()) {
    header('Location: ' . $result->challengeUrl());
    exit;
}

// Visitor passed — render your page normally

CrytifyResult methods

MethodReturnsDescription
isAllow() bool true when the visitor passed.
isChallenge() bool true when additional verification is recommended.
isBlock() bool true when the visitor should be denied.
challengeUrl() ?string Hosted Crytify challenge URL when the decision is CHALLENGE.
decision() string Raw decision string: ALLOW, CHALLENGE, or BLOCK.
trustScore() int 0–100. Higher is more trusted.
riskScore() int 0–100. Higher is more suspicious.
reason() string Human-readable reason string from the decision.
isFailOpen() bool true when a network error caused automatic fail-open.

CodeIgniter 4 Filter

For CI4 projects, use the provided filter class to protect routes declaratively.

// app/Config/Filters.php
public array $aliases = [
    'crytify' => \App\Filters\CrytifyGateFilter::class,
];

// app/Config/Routes.php — protect individual routes
$routes->get('checkout', 'OrderController::checkout', ['filter' => 'crytify']);

// Or protect an entire group
$routes->group('members', ['filter' => 'crytify'], function ($routes) {
    $routes->get('dashboard', 'MemberController::dashboard');
    $routes->get('profile',   'MemberController::profile');
});

Browser Fingerprint

Passing a fingerprint object in /v1/trust adds client-side consistency context that is not available from server headers alone. Exact signal handling is private and public responses only expose broad reason categories.

Public categories

SignalWhat it meansConfidence
fingerprint_risk Client-side consistency context contributed to the decision. public category
automation_risk Automation-related context contributed to the decision. public category
identity_risk Session or identity consistency context contributed to the decision. public category

Collect fingerprint with the JS helper

crytify-fingerprint.js collects all signals and returns a plain object. Use it independently if you want to collect the fingerprint yourself and pass it to your server without the automatic page-hiding behavior.

<script src="/crytify-fingerprint.js"></script>
<script>
crytifyFingerprint().then(function (fp) {
    // fp is a plain JS object — send it to your server
    fetch('/api/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            email:       document.getElementById('email').value,
            password:    document.getElementById('password').value,
            fingerprint: fp,   // <-- include fingerprint in your form submission
        }),
    });
});
</script>

Your server then passes fingerprint directly to /v1/trust in the request body. No new Crytify endpoint is needed.