Getting started
Integrate with AI coding assistants
Instructions to give an AI coding assistant so it integrates isCredible correctly: where to call it, what to send, and the rules to follow.
Integrate isCredible: instructions for AI coding assistants
This page is written for AI coding assistants (Claude Code, Cursor, GitHub Copilot, Windsurf and similar) and for the developers who use them. Give your assistant this page, or its plain-text copy at https://iscredible.com/ai-integration.md, and ask it to integrate isCredible. It describes what to build, where, and the rules to follow.
What isCredible is#
isCredible is an HTTP API that checks a user action (a signup, login, trial start, form submission or expensive action) and returns one decision:
APPROVE: nothing worrying. Continue.REVIEW: some risk, not conclusive. Add a light step (email or phone code, smaller free allowance) or flag it.DENY: strong signs of abuse. Block, with a neutral message.
Each response also has a risk score from 0 to 100 and plain-language reasons. There is one endpoint for checks: POST https://api.iscredible.com/v1/credibility/verify.
Rules for the assistant#
Follow these in every integration.
- Call isCredible only from server-side code. Never from browser, mobile or desktop client code. The API key must never reach a client.
- Read the key from an environment variable named
ISCREDIBLE_API_KEY. Never hard-code it, never commit it, never log it. Add it to.env.examplewith an empty value and to the deployment's secret settings. - Fail open. If isCredible times out, is unreachable or returns a 5xx or 429, continue as if the decision were
APPROVEand log the error. Use a short timeout (about 3 seconds) and at most one retry on user-facing paths. - Check at events, not on every request. Signups, logins, trial starts, form submissions, card additions and expensive actions. Do not call it on page views or for every API request; for metered usage use the SDK's
checkUsage, which caches decisions. - Send only what the app already has. Never send passwords, full card numbers, CVV codes or government ID numbers. For cards, send the payment processor's card fingerprint (for Stripe,
payment_method.card.fingerprint). - Keep reasons internal. Show end users a neutral message such as "We couldn't create your account. Contact support if this is a mistake." Log
requestIdand the reasons for the team. - Do not change the app's own security. isCredible adds a decision; it does not replace authentication, rate limiting, input validation or payment checks.
- Ask the developer before changing user-facing behaviour that is not described here, such as blocking existing users or deleting accounts.
Install#
Use the official SDK when the backend is JavaScript/TypeScript or Python. Otherwise, call the HTTP API directly with any HTTP client.
# Node.js 18+, serverless and edge runtimes (no dependencies)
npm install is-credible
# Python 3.9+ (no dependencies)
pip install is-credibleCreate one client at startup and reuse it.
// server/iscredible.ts
import { IsCredibleClient } from "is-credible";
export const iscredible = new IsCredibleClient({
apiKey: process.env.ISCREDIBLE_API_KEY!,
timeoutMs: 3000,
maxRetries: 1
});# app/iscredible_client.py
import os
from iscredible import IsCredible
iscredible = IsCredible(api_key=os.environ["ISCREDIBLE_API_KEY"], timeout=3, max_retries=1)Where to integrate#
Find these points in the codebase. Integrate the ones that exist; skip the rest. If the app has none of them, ask the developer what they want to protect.
| Integration point | Find it by looking for | Fields to send |
|---|---|---|
| Signup / registration | the handler that creates the user record | email, ip, userAgent, deviceId (if available), accountCreation: true |
| Login | the handler that runs after the password or OAuth check succeeds | userId, email, ip, userAgent, deviceId, loginAttempts |
| Free trial start / card added | trial creation, subscription creation, payment-method webhooks | userId, email, ip, deviceId, paymentFingerprint, accountCreation: true |
| Public form (application, order, lead, contact, waitlist, review) | the handler that stores a submission or emails the team | eventType: "submission", email, phone, ip, userAgent, headers, timeToSubmitMs |
| Expensive or metered action (AI request, export, credits) | code that calls an LLM, spends credits or checks a quota | use checkUsage with the user's current usage |
Getting the client IP: use the framework's trusted client IP (for example req.ip in Express with trust proxy set, request.client.host in FastAPI, x-forwarded-for set by your own load balancer). Do not trust a client-supplied IP header without a proxy you control.
A device ID is optional but makes repeat-account detection much stronger. If the app has a browser frontend, create a random ID on first visit, keep it in localStorage, and send it to the backend with signup and login requests:
// browser
export function getDeviceId(): string {
let id = localStorage.getItem("device_id");
if (!id) {
id = crypto.randomUUID();
localStorage.setItem("device_id", id);
}
return id;
}Signup#
Check before creating the user record and before sending any email.
import { decide } from "is-credible";
import { iscredible } from "./iscredible";
export async function signup(req, res) {
const { email, deviceId } = req.body;
let action = "APPROVE";
try {
const result = await iscredible.verify({
email,
ip: req.ip,
userAgent: req.get("user-agent"),
deviceId,
accountCreation: true
});
action = decide(result).action;
if (action !== "APPROVE") log.info("isCredible", { requestId: result.requestId, action, reasons: decide(result).reasons });
} catch (error) {
log.warn("isCredible unavailable, allowing signup", { error }); // fail open
}
if (action === "DENY") {
return res.status(403).json({ error: "We couldn't create your account. Contact support if this is a mistake." });
}
const user = await createUser({ email, needsVerification: action === "REVIEW" });
return res.status(201).json({ id: user.id });
}from iscredible import IsCredibleError, decide
from .iscredible_client import iscredible
def signup(request):
data = request.json
action = "APPROVE"
try:
result = iscredible.verify({
"email": data["email"],
"ip": client_ip(request),
"userAgent": request.headers.get("User-Agent"),
"deviceId": data.get("deviceId"),
"accountCreation": True,
})
action, reasons = decide(result)
if action != "APPROVE":
log.info("isCredible %s %s %s", result["requestId"], action, reasons)
except (IsCredibleError, ValueError) as error:
# IsCredibleError: API unavailable or rejected the call. ValueError: malformed input, e.g. an invalid email.
log.warning("isCredible check skipped, allowing signup: %s", error) # fail open
if action == "DENY":
return {"error": "We couldn't create your account. Contact support if this is a mistake."}, 403
user = create_user(data["email"], needs_verification=(action == "REVIEW"))
return {"id": user.id}, 201What REVIEW should do at signup: create the account but hold back the valuable part, such as requiring email or phone verification before free credits are granted.
Login#
Call after the credentials check succeeds. Always send userId: per-user history only works with a stable ID.
const result = await iscredible.verify({
userId: user.id,
email: user.email,
ip: req.ip,
userAgent: req.get("user-agent"),
deviceId: req.body.deviceId,
loginAttempts: failedAttemptsBeforeThisLogin
});
// APPROVE: continue. REVIEW: ask for a second factor or email a confirmation link.
// DENY: end the session, notify the account owner, require a password reset.Form submissions#
Use the built-in Submission Screening scenario. No browser script or captcha is needed.
const result = await iscredible.verify(
{
eventType: "submission",
email: form.email,
phone: form.phone,
ip: req.ip,
userAgent: req.get("user-agent"),
headers: { accept: req.get("accept"), "accept-language": req.get("accept-language") },
timeToSubmitMs: Date.now() - Number(form.renderedAt) // optional: render the time into a hidden field when you show the form
},
{ scenarioId: "SYS_SUBMISSION_SCREENING_001" }
);
// APPROVE: save. REVIEW: ask for an email or phone code first, or save it flagged.
// DENY: return the normal success response but do not save or forward it.For applications such as credit or insurance, use the result to screen out bots and fake submissions, never to decide eligibility.
Metered usage (AI tokens, API calls, credits)#
The app keeps counting usage and enforcing its own limits. Before an expensive action, pass the user's current total to checkUsage. The SDK only calls the API when a check is due, so calling it on every expensive request is fine.
const { action } = await iscredible.checkUsage(
user.id,
{ metric: "ai_tokens", used: tokensUsedThisPeriod, limit: user.planTokenLimit },
{ deviceId, ip: req.ip, accountCreatedAt: user.createdAt }
);
if (action === "DENY") return res.status(429).json({ error: "Free limit reached. Upgrade to continue." });
if (action === "REVIEW") { /* for example: require phone verification before more free usage */ }decision = iscredible.check_usage(
user.id,
{"metric": "ai_tokens", "used": tokens_used, "limit": plan_token_limit},
{"deviceId": device_id, "ip": ip, "accountCreatedAt": user.created_at.isoformat()},
)
if decision["action"] == "DENY":
return {"error": "Free limit reached. Upgrade to continue."}, 429Metric names are lower-case, such as ai_tokens, api_calls, credits, exports. Wrap the call in the same fail-open handling as above.
Without an SDK (any language)#
curl https://api.iscredible.com/v1/credibility/verify \
-H "x-api-key: $ISCREDIBLE_API_KEY" \
-H "content-type: application/json" \
-d '{ "userData": { "email": "jane@example.com", "ip": "203.0.113.7", "accountCreation": true } }'Request body:
| Field | Type | Notes |
|---|---|---|
userData | object | The data to check. Unknown fields are ignored; missing fields just skip the checks that need them. |
scenarioId | string, optional | Run one scenario, e.g. SYS_SUBMISSION_SCREENING_001. |
scenarioIds | string[], optional | Run several scenarios. |
Without scenarioId, all of the account's active scenarios run; an account with no active scenarios of its own uses the built-in set. When a specific built-in scenario is wanted, pass its scenarioId explicitly.
Response (abridged):
{
"requestId": "6f3c8a6e-2b8e-4b6b-9b0e-7c1a5d2f9e10",
"overallRecommendation": "REVIEW",
"overallRiskScore": 50,
"summary": "Needs review (50.0). ...",
"scenarioResults": [
{
"scenarioId": "SYS_SUBMISSION_SCREENING_001",
"recommendation": "REVIEW",
"ruleReports": [
{ "ruleId": "...", "status": "FLAGGED", "findings": [{ "code": "DISPOSABLE_EMAIL_DETECTED", "message": "..." }] }
]
}
]
}Branch on overallRecommendation. Use overallRiskScore to choose a REVIEW step (lower score: a light check; higher: a closer look). Finding codes are stable for logging and metrics; messages are for people.
Errors#
| Status | Meaning | What the code should do |
|---|---|---|
| 200 | Decision returned | Use overallRecommendation |
| 400 | Invalid request body | Fix the request; errors lists the problems. Do not retry. |
| 401 / 403 | API key missing, unknown or revoked | Log loudly; fail open for the user; alert the developer |
| 404 | Unknown scenarioId | Fix the ID |
| 429 | Rate limit or daily quota reached | Fail open; retry later |
| 5xx, timeout | Temporary problem | Fail open; log |
The SDKs raise IsCredibleError with status (0 for network errors and timeouts) and retryable, and already retry 429 and 5xx with backoff. They also reject malformed input (such as an invalid email address) before sending anything: the JavaScript SDK throws an Error, the Python SDK raises ValueError. Treat those like any other failure: fail open, or validate the input first.
Testing the integration#
- Use a separate test account or clearly fake identities; checks are remembered and affect later decisions on the same account.
- A throwaway address such as
test@mailinator.comsent toSYS_DISPOSABLE_EMAIL_001should returnDENY. A normal address such asyou@yourcompany.comshould returnAPPROVE. - Write unit tests that mock the client and cover all three decisions plus a thrown error (fail open).
- To remove a test identity afterwards, call
POST https://api.iscredible.com/v1/credibility/erase(oriscredible.erase(...)) with its email, user ID, device ID, phone or IP. - The free plan allows 100 checks a day; avoid calling isCredible in load tests.
Data and privacy#
- Data is used only to make decisions for the account that sent it.
- To honour an end-user's deletion request, call the Erase API (
client.erase({ email, userId })in the SDKs). - Mention isCredible (fraud and abuse prevention) in the app's privacy policy as a service provider that receives sign-up and usage signals.
Checklist for the assistant#
Before finishing, confirm each item:
- The key is read from
ISCREDIBLE_API_KEY, is in.env.examplewith no value, and is never sent to a client or logged. - Every call runs on the server, at an event (signup, login, trial, form, expensive action), not on page views.
- Each call has a short timeout and fails open with a log line.
-
DENY,REVIEWandAPPROVEeach have defined behaviour; end users see neutral messages only. -
requestIdand reasons are logged for non-approved decisions. - No passwords, card numbers or government IDs are sent.
- Tests cover all three decisions and the error path.
- The developer is told what changed, which env var to set, and how to get a key (sign up at iscredible.com, then open API key in the dashboard).
Prompt to give your assistant#
Copy this into Claude Code, Cursor or another coding assistant:
Integrate isCredible into this app, following https://iscredible.com/ai-integration.md exactly.
Protect: signup, and any public forms. (Edit this list: login, free trial start, AI/metered usage.)
Use the official SDK for our backend language, read the key from ISCREDIBLE_API_KEY, call it only
from server code, fail open on errors, and keep reasons out of user-facing messages.
Show me the plan before editing, then add tests for APPROVE, REVIEW, DENY and the error path.Reference#
- Docs: https://iscredible.com/docs
- Verify API: https://iscredible.com/docs/api/verify and request fields: https://iscredible.com/docs/api/fields
- Response: https://iscredible.com/docs/api/response
- Built-in scenarios: https://iscredible.com/docs/scenarios
- SDKs: https://iscredible.com/docs/sdks/javascript and https://iscredible.com/docs/sdks/python
- Support: solutions@iscredible.com