Embed an assistant on any website. Drop in the chat widget for booking and service questions, or wire your own forms straight to your inbox — both powered by your FieldStone account.
A floating assistant that chats, qualifies visitors, and books appointments. One script tag, or a React component.
Set up the widget →Headless — keep your own form UI and POST submissions straight to the email and phone bound to your key. No widget, no stored data.
Send a submission →Every snippet below authenticates with two values from a FieldStone account. Grab them once and drop them into FieldStone.init().
your-business-id placeholder.fsk_. Required for free-agent forms and recommended for the widget; replaces fsk_your_api_key.Integrating on behalf of a business? Ask the account owner to send you their Business ID and a freshly generated API key from that screen. Free-agent keys are issued separately by a FieldStone super admin, bundled with the notify email and phone — see Free Agent Forms. Keep keys private and never commit them to a public repository.
Add these two lines before your closing </body> tag. Works with any website — no build step required. Swap in your Business ID and API key.
<script src="https://pub-cc5ed544ca014859991114c0d7808ed7.r2.dev/v1/sdk.js"></script>
<script>
FieldStone.init({
businessId: "your-business-id",
apiKey: "fsk_your_api_key",
});
</script>For React, Vue, Svelte, or any project with a build step.
npm install @fieldstoneapp/sdk
Import the component and wrap your app.
import { FieldStoneAI } from "@fieldstoneapp/sdk";
function App() {
return (
<FieldStoneAI
businessId="your-business-id"
apiKey="fsk_your_api_key"
>
<YourApp />
</FieldStoneAI>
);
}Options passed to FieldStone.init() or the <FieldStoneAI> component.
| Option | Type | Description |
|---|---|---|
| businessId* | string | Your business identifier. Required for the chat widget; omit it in free-agent mode. |
| apiKey | string | Your SDK API key from Settings → SDK. Defaults to businessId for public-key flows. Required in free-agent mode. |
| mode | string | "widget" (default) mounts the chat widget. "free_agent" is headless — no widget, no bootstrap, only submitForm(). See Free Agent Forms below. |
| apiUrl | string | Override the FieldStone API base URL. Only needed for self-hosted or staging environments. |
| position | string | "bottom-right", "bottom-left", "top-right", or "top-left". |
| theme | object | Override colors: primaryColor, fontFamily, borderRadius, dark. |
| voice | object | { enabled: boolean } — toggle the push-to-talk mic button in the chat input. |
| context | string | Extra business info (pricing, policies, hours) sent with every message. Silently capped at 2000 characters. See Business Context below. |
The widget mounts itself and runs on its own, but you can drive it from your own UI via the singleton on FieldStone.getInstance(). A second FieldStone.init() call warns and returns the existing instance.
// Open / close the chat panel from your own UI FieldStone.getInstance()?.open(); FieldStone.getInstance()?.close(); // Wait for the bootstrap call to finish const business = await FieldStone.getInstance()?.ready(); // Tear it down completely (e.g. on logout) FieldStone.getInstance()?.destroy();
Pass short, business-specific facts that the assistant should know on every turn — pricing rules, service minimums, seasonal hours, promotions. The string is forwarded to the agent as background data (not instructions), so it's a safe place for facts, not for telling the assistant how to behave.
FieldStone.init({
businessId: "your-business-id",
apiKey: "fsk_your_api_key",
context: `
Emergency plumbing calls answered 24/7.
Minimum call-out fee: $150 (waived if you book a full repair).
Service area: within 40 miles of downtown.
`,
});
// Update context later — the next message the visitor sends
// will include the new value.
FieldStone.getInstance()?.setContext("Updated pricing: $175 minimum.");setContext() updates apply to the very next message.Keep your own form and design. A free-agent key runs headless — no chat widget mounts and there's no bootstrap call. You collect your own fields and submit them; the server fans each submission out to the email and phone bound to the key and stores no visitor data — only a non-PII usage event. Destinations are server-bound to the key and can't be set from the client.
Keys are issued by a FieldStone super admin together with the notify email, phone, and logo. The same key is used in every snippet below.
No SDK required. POST your fields with the key in the X-API-Key header.
await fetch(
"https://stone-production-d2ea.up.railway.app/api/sdk/free-agent/submit",
{
method: "POST",
headers: {
"X-API-Key": "fsk_your_free_agent_key",
"Content-Type": "application/json",
},
body: JSON.stringify({
fields: [
{ label: "Name", value: "Jane Doe" },
{ label: "Project", value: "Kitchen remodel" },
],
images: ["https://example.com/site-photo.jpg"], // optional, https only
}),
},
);Initialize with mode: "free_agent" and call FieldStone.submitForm() from your own submit handler. No chatbot appears.
<script src="https://pub-cc5ed544ca014859991114c0d7808ed7.r2.dev/v1/sdk.js"></script>
<script>
// Headless: no chat widget mounts, no bootstrap call.
FieldStone.init({
apiKey: "fsk_your_free_agent_key",
mode: "free_agent",
});
// Submit from your own form's handler.
FieldStone.submitForm({
fields: [{ label: "Name", value: "Jane Doe" }],
images: [],
});
</script>useFreeAgentSubmit lazily initializes a headless instance and returns submit plus loading / error state.
import { useFreeAgentSubmit } from "@fieldstoneapp/sdk";
function ContactForm() {
const { submit, loading, error } = useFreeAgentSubmit({
apiKey: "fsk_your_free_agent_key",
});
async function onSubmit(e: React.FormEvent) {
e.preventDefault();
const result = await submit({
fields: [{ label: "Name", value: "Jane Doe" }],
images: [],
});
// result → { ok, email, sms }
}
return (
<form onSubmit={onSubmit}>
{/* your own fields */}
<button type="submit" disabled={loading}>Send</button>
{error && <p>{error.message}</p>}
</form>
);
}A successful submit resolves with each channel's dispatch result. A non-2xx response rejects (the hook surfaces it as error).
{ "ok": true, "email": true, "sms": true }{ label, value } pairs. Labels max 100 chars, values max 2000.https:// URLs, embedded inline in the notification email.free_agent_submitted usage event (counts + origin) is recorded.Use the same submitForm({ fields, images }) call with your own account API key (from Settings → SDK) instead of a free-agent key. Rather than an email relay, each submission is saved into your CRM: a contact is created or matched, the full field list is logged as a note, and any image URLs are downloaded into StoneBox and linked to the contact.
The identity fields Name, Email, Phone, and Address map onto the contact (matched by email, then phone); every field is kept verbatim in the note. The submit resolves with the contact instead of the notify flags.
{ "ok": true, "contactId": "…", "created": true, "photosSaved": 1 }The widget never collects identifying data until the visitor explicitly enters it (name, phone, email) during a booking. It also respects browser-level privacy signals at init time:
navigator.globalPrivacyControl === true)navigator.doNotTrack === "1")When either signal is set, all client-side analytics calls become no-ops for the entire session. The chat itself keeps working.
Need help? support@fieldstoneapp.com