INTEL
Status: blockedCLUSTERbushehr shipping company limited added — likelyStatus: blockedCLUSTERNovorossiysk-Turkish-Med Dark Fleet Cluster added — confirmedStatus: blockedCLUSTERPinnacle Petrol LLC added — likelyStatus: blockedCLUSTERArrakis Development added — likelyStatus: blockedCLUSTERExxon Global Distributor added — likelyStatus: pendingCORPUS427 entities · 63 countries
← All recipes

Drop-in React intake-form integration

Pre-screen counterparties against OilFlow before saving to your DB.

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_id is the receipt id; counterparties can verify it at https://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