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://crytify.com/register 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.

OPTIONAL

Read visitor data from $_SESSION.

crytify_gate() already stores the visitor's network and device data in $_SESSION['crytify'] automatically — nothing extra to call for that. If you'd rather read flat variables like $_SESSION['country'] directly, call crytify_populate_session() once, anywhere after crytify_gate(). This is opt-in on purpose: the flat keys use common names (country, city, timezone...) that could collide with session variables your app already uses for something else, so nothing is written unless you ask for it. The last five keys (hasCam, hasTouchscreen, timezoneD, browserR, deviceR) are client-collected device signals — they appear from a visitor's second gated request onward (once the browser fingerprint has been collected), stay null until then, and are client-reported so treat them as convenience data for personalization, not as a security check.

Usage

<?php
session_start();
require_once __DIR__ . '/crytify.php';
crytify_gate('cry_live_xxxxxxxxxxxx');
crytify_populate_session(); // optional

echo $_SESSION['country'];       // "Indonesia"
echo $_SESSION['isp'];           // "PT. Cyberindo Aditama"
echo $_SESSION['browser_name'];  // "Chrome"
?>

Available keys

asnisp organizationcountry countryCodecontinent regioncity timezoneutcOffset latlon browser_namebrowser_version os_nameos_version getDeviceNamegetBrandName getModeldetailsdevice hasCamhasTouchscreen timezoneDbrowserR deviceR

OPTIONAL

Log every visitor with on_result.

crytify_populate_session() only fills $_SESSION on ALLOW — a CHALLENGE or BLOCK outcome renders its own page and exits immediately, so any code you put after crytify_gate() never runs for those visitors. If you want to record every attempt — not just the ones that passed — pass an on_result callback in the options array. It runs for all three decisions, right before the challenge/block page is shown (or before the page renders normally, on ALLOW).

Usage

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

crytify_gate('cry_live_xxxxxxxxxxxx', [
    'on_result' => function (
        string $decision,
        array $network,
        array $device
    ) {
        $line = date('Y-m-d H:i:s') . " | {$decision} | "
            . ($network['country'] ?? '-') . ' | '
            . ($network['isp'] ?? '-') . ' | '
            . ($device['browser_name'] ?? '-') . "\n";
        file_put_contents(
            __DIR__ . '/visitors.txt',
            $line,
            FILE_APPEND | LOCK_EX
        );
    },
]);
?>

Notes

  • Called with ($decision, $network, $device, $result) — same shape as $_SESSION['crytify'], plus the raw API response as the 4th argument.
  • A visitor still within their 30-minute pass-cookie grace period skips the API call entirely, so on_result doesn't fire for those requests — nothing new to log, same visitor as last time.
  • If your callback throws, it's caught and ignored — a logging bug never blocks or breaks the actual gate for real visitors.

SAVE CREDITS

Protect extra pages for free with crytify_require_pass().

Every full crytify_gate() call costs credits, because it runs the real detection pipeline. If your site has one clear "front door" page — a landing page, a login page, a product page — that every real visitor already passes through before reaching other protected pages (checkout, account, member area), you don't need to re-run the full check on every single one of them. crytify_gate() already sets a signed _crytify_pass cookie for 30 minutes after an ALLOW. crytify_require_pass() is a lightweight, zero-credit check for those other pages: it only reads that cookie — no API call, no detection pipeline, no charge.

If the cookie is missing or expired (visitor skipped the front-door page entirely — a common bot/scraper tell, since real browsing arrives via a click path, not a cold direct hit), the visitor is redirected to the entry page you specify, with a return-to parameter, so they land back on the original page right after passing the real check there. This still shows up in Live Traffic (as a lightweight logged event, not a full trust decision) so the pattern isn't invisible — it just never costs a credit.

Entry page (checked normally)

<?php
require_once __DIR__ . '/crytify.php';
crytify_gate('cry_live_xxxxxxxxxxxx');

// Page renders normally. On ALLOW, the
// _crytify_pass cookie is set automatically.
?>

Other protected pages (free)

<?php
require_once __DIR__ . '/crytify.php';
crytify_require_pass(
    'cry_live_xxxxxxxxxxxx',
    'https://yoursite.com/entry.php'
);

// Reached only if the cookie was valid —
// zero credits charged either way.
?>
Only use this for pages a real visitor always reaches after your entry page in normal use. If a page can legitimately be the very first page someone lands on (from an ad, a bookmark, a shared link with no prior visit), keep using full crytify_gate() there instead — otherwise every one of those legitimate direct visits gets bounced through an extra redirect.

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. ↳ JavaScript SDK

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",
  "risk_level": "medium",
  "reasons": [
    "Additional verification is required before this request can proceed."
  ],
  "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
  }
}

Reading the decision

Branch on decision (ALLOW / CHALLENGE / BLOCK) — that is the field to act on. risk_level is a coarse band (low / medium / high) for display only; it tracks the decision and is not a precise score. The raw numeric trust_score / risk_score are no longer returned. Keys created before this change keep the old numeric fields until you upgrade them from Integration → your key → “Upgrade format” (one-way). A full per-signal breakdown stays available in your dashboard.

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.

Test mode

Add "test": true to the request body while exploring the integration flow. The real decision engine still runs — same ALLOW/CHALLENGE/BLOCK you'd get in production — but nothing from that request is written back into Crytify's self-learning data (IP, ASN, or fingerprint reputation). Credits are still charged normally, and the request still shows up in your dashboard tagged as a test request.

Never send test: true from real visitor traffic — it's for your own exploratory/integration testing only. Repeated testing without it (e.g. via curl, from the same IP, over and over) can build up real self-learned reputation against your own IP, exactly like any other traffic would.

Testing directly on your own site with crytify_gate()? Pass it in the options array instead of editing the request body yourself:

crytify_gate('cry_live_xxxxxxxxxxxx', ['test' => true]);

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)

Only your entry page needs the full check. If visitors reliably reach other protected pages (checkout, account) after a page that already ran crytify_gate(), use crytify_require_pass() on those instead — a free, cookie-only check with no API call and no credits charged. See how it works →

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.
riskLevel() ?string Coarse risk band on hardened keys: low, medium, or high. null on legacy keys.
riskScore() ?int 0–100 on legacy keys only; null on hardened keys (use riskLevel()).
trustScore() ?int Deprecated. Always null — the raw trust score is no longer returned.
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="/assets/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.