Recipe 02 — Drop-in React intake-form integration
20 minutes. Result: a <CounterpartyIntakeForm /> React component your engineers paste into any internal app to screen counterparties against OilFlow before saving them to your DB.What this recipe does
A bank trade-finance desk has an internal intake form. Today a clerk types a counterparty's name and saves it. After this recipe, the form pre-screens against OilFlow's KYC + cluster + regulatory APIs in <2s and surfaces the verdict inline.
Step 1 — Add a server route that forwards to OilFlow
Your front-end should never carry the OilFlow API key (it would leak to every browser). Proxy through your backend:
// app/api/screen/route.ts (Next.js example)
import OilFlow from "@oilflow/sdk";
const client = new OilFlow({ apiKey: process.env.OILFLOW_API_KEY });
export async function POST(req: Request) {
const body = await req.json();
const result = await client.kyc.screen({
company_name: body.company_name,
country: body.country,
directors: body.directors,
});
return Response.json(result);
}For Express/Fastify/Flask, the shape is identical — receive JSON, call client.kyc.screen(...), return the result.
Step 2 — The React component
// components/CounterpartyIntakeForm.tsx
"use client";
import { useState } from "react";
type Verdict = "pass" | "review" | "fail";
interface ScreenResult {
verdict: Verdict;
verdict_reasoning: string;
screening_run_id: string;
}
export default function CounterpartyIntakeForm({
onSave,
}: {
onSave: (entity: { name: string; verdict: Verdict; runId: string }) => void;
}) {
const [name, setName] = useState("");
const [country, setCountry] = useState("");
const [directors, setDirectors] = useState("");
const [result, setResult] = useState<ScreenResult | null>(null);
const [error, setError] = useState<string | null>(null);
const [busy, setBusy] = useState(false);
async function screen(e: React.FormEvent) {
e.preventDefault();
setBusy(true);
setError(null);
setResult(null);
try {
const res = await fetch("/api/screen", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
company_name: name.trim(),
country: country.trim() || undefined,
directors: directors
.split(",")
.map((d) => d.trim())
.filter(Boolean),
}),
});
if (!res.ok) {
setError(`Screen failed (${res.status})`);
return;
}
const body = (await res.json()) as ScreenResult;
setResult(body);
} catch (err) {
setError(err instanceof Error ? err.message : "Network error");
} finally {
setBusy(false);
}
}
function save() {
if (!result || !name) return;
onSave({ name, verdict: result.verdict, runId: result.screening_run_id });
}
return (
<form onSubmit={screen} style={{ display: "flex", flexDirection: "column", gap: 12 }}>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Counterparty legal name"
required
/>
<input
value={country}
onChange={(e) => setCountry(e.target.value)}
placeholder="Jurisdiction (e.g. UAE)"
/>
<input
value={directors}
onChange={(e) => setDirectors(e.target.value)}
placeholder="Directors (comma-separated)"
/>
<button type="submit" disabled={busy || !name}>
{busy ? "Screening…" : "Pre-screen"}
</button>
{error && <p style={{ color: "red" }}>{error}</p>}
{result && (
<div
style={{
padding: 12,
border: "1px solid",
borderColor: result.verdict === "fail" ? "red" : result.verdict === "review" ? "orange" : "green",
borderRadius: 4,
}}
>
<strong>Verdict: {result.verdict.toUpperCase()}</strong>
<p>{result.verdict_reasoning}</p>
<p>
Receipt:{" "}
<a href={`https://oilflow.us/verify/${result.screening_run_id}`} target="_blank" rel="noopener">
{result.screening_run_id.slice(0, 8)}
</a>
</p>
{result.verdict !== "fail" && (
<button type="button" onClick={save}>
Save counterparty
</button>
)}
</div>
)}
</form>
);
}Step 3 — Wire it in
<CounterpartyIntakeForm
onSave={({ name, verdict, runId }) => {
// Save to your DB, kick off your downstream workflow
}}
/>Behavior notes
- `verdict: "fail"` — confirmed cluster match OR regulatory tradability blocker. The component disables the Save button.
- `verdict: "review"` — likely cluster match or queued check needs human review. Save allowed but flagged.
- `verdict: "pass"` — green-light.
- The
screening_run_idis the receipt id; counterparties can verify it athttps://oilflow.us/verify/{id}independently of your bank.
Common gotchas
- Don't call OilFlow directly from the browser. Proxy through your server. Your API key stays server-side.
- Rate limit: 30 KYC screens / minute on default plan. Wrap with a debounce if the user types fast.
Next steps
- Recipe 03: Watchlist polling from your CRM
- Recipe 04: UBO traversal + risk export