Quickstart
Go from zero to a working React integration in five minutes.
This walkthrough wires @easylabs/react into a fresh app, mounts a card element, and runs a one-shot checkout that creates a customer, tokenizes a card, and charges it — all from the browser, with the card number never touching your server.
1. Get your API keys
Open the Easy Labs dashboard and copy your publishable API key from the Developers tab.
- Test keys start with
sk_test_and automatically point the SDK athttps://sandbox-api.itseasy.co/v1/api. - Live keys point at
https://api.itseasy.co/v1/api.
There is no separate "publishable" vs. "secret" key for the React SDK — the key you paste into EasyProvider is the one used for both API calls and Basis Theory session creation, so use a test key in development.
Store it as an environment variable. For Vite, that's VITE_EASY_API_KEY; for Next.js, NEXT_PUBLIC_EASY_API_KEY.
2. Initialize the SDK
Mount EasyProvider once at the top of your tree.
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { EasyProvider } from "@easylabs/react";
import App from "./App";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<EasyProvider apiKey={import.meta.env.VITE_EASY_API_KEY}>
<App />
</EasyProvider>
</StrictMode>,
);EasyProvider validates the key, opens a Basis Theory session, and exposes the entire API surface through useEasy(). See EasyProvider for the full prop and hook reference.
3. Make your first call
Render a CardElement and call checkout from useEasy(). The cardElement ref is passed straight into the source — the SDK handles tokenization and charge creation in a single call.
import { CardElement, useEasy, type ICardElement } from "@easylabs/react";
import { useRef, useState } from "react";
export default function App() {
const { checkout } = useEasy();
const cardRef = useRef<ICardElement>(null);
const [status, setStatus] = useState<"idle" | "loading" | "ok" | "err">("idle");
const [orderId, setOrderId] = useState<string>();
async function onSubmit(e: React.FormEvent) {
e.preventDefault();
setStatus("loading");
try {
const res = await checkout({
customer_creation: true,
customer_details: {
first_name: "Ada",
last_name: "Lovelace",
email: "ada@example.com",
},
line_items: [{ price_id: "price_123", quantity: 1 }],
source: {
type: "PAYMENT_CARD",
name: "Ada Lovelace",
cardElement: cardRef,
},
});
if (res.success) {
setOrderId(res.data.orderId);
setStatus("ok");
} else {
setStatus("err");
}
} catch {
setStatus("err");
}
}
return (
<form onSubmit={onSubmit}>
<CardElement ref={cardRef} id="card" />
<button type="submit" disabled={status === "loading"}>
{status === "loading" ? "Processing…" : "Pay"}
</button>
{status === "ok" && <p>Order {orderId} created.</p>}
{status === "err" && <p>Payment failed.</p>}
</form>
);
}Replace price_123 with a real price ID from your dashboard.
4. Handle the response
checkout returns an ApiResponse<CheckoutResult>:
- Success —
res.success === true.res.datacontainsorderId(the new order's id), an optionaltransferfor one-shot payments, andsubscriptions(an array of created subscription rows when the line items include recurring prices). The customer and payment-instrument records are not echoed back here — fetch them withgetCustomer(...)orgetCustomerPaymentInstruments(...)if you need them. - API error —
res.success === falsewith amessagefield. Surface it to the user. - Tokenization or network error —
checkoutthrows. Wrap the call intry/catch; the example above does this.
Test cards live in the Easy Labs dashboard under Developers → Test data. Use 4111 1111 1111 1111 with any future expiry and any 3-digit CVC for an approved test charge.
What's next
- EasyProvider — every method you can pull off
useEasy(). - Elements — split-field cards, bank accounts, and styling.
- Embedded Checkout — skip the form-building entirely with the hosted iframe.
- Examples — full Vite SPA and Next.js apps you can clone and run.