Backprompter Browser SDK
Add Backprompter AI agents to any web app. One script gives you end-user auth, agent calls, voice, conversation history, artifacts, custom data, and vector search, all scoped to your app.
Overview
The SDK ships as a hosted bundle. There is nothing to build or install from source. Include the script, set your App Key, sign a user in, then call an agent.
Get your App Key
Your App Key identifies your app and tenant. Find it in the Backprompter dashboard. Open your app, then the Deploy modal. It also appears on the API Reference page.
Install
Include the bundle from the CDN. No build step is required.
<script src="https://backprompter-sdk.pages.dev/backprompter-full.min.js"></script>
<!-- Optional: only if your agents return math in artifacts -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css" />
Two bundles are published.
| Bundle | Contains |
|---|---|
backprompter-core.min.js |
Auth, run, and voice. |
backprompter-full.min.js |
Core, plus file reading and artifact rendering with Markdown and math. Use this one unless you need the smallest bundle. |
The SDK is exposed as the global Backprompter.
Quick start
<script src="https://backprompter-sdk.pages.dev/backprompter-full.min.js"></script>
<script>
Backprompter.setAppKey('YOUR_APP_KEY'); // 1. identify your app
await Backprompter.signInAnonymously(); // 2. sign the end user in
const r = await Backprompter.run({ userPrompt: 'Hello! What can you do?' });
console.log(r.response); // 3. call an agent
</script>
Configuration
Backprompter.setAppKey('YOUR_APP_KEY'); // required
Backprompter.setBackendUrl('https://…'); // only if you self-host the backend
Backprompter.setEnvironment('production'); // or 'local', shorthand for setBackendUrl
Backprompter.getBackprompterURL(); // the backend URL currently in use
Authentication
Use whichever methods you enabled for your app.
await Backprompter.signIn(email, password); // email and password
await Backprompter.signUp(email, password); // self-service registration
await Backprompter.signInAnonymously(); // guest
await Backprompter.signInWithGoogle(); // Google OAuth popup
await Backprompter.signInWithSecret('your-code'); // shared secret code
await Backprompter.signOut();
const user = await Backprompter.currentUser(); // null when signed out
await Backprompter.onAuthChange((user) => {
console.log(user ? `Signed in as ${user.email}` : 'Signed out');
});
signUp works only when self sign-up is enabled for the app. If you turned it off,
create accounts from the dashboard under Users, then
User Authentication. That either emails the person a link to choose their own
password, or gives you a generated one to pass on.
Use your own login
You do not have to use Backprompter auth. If your app already has a login, keep it. Your backend
mints a session token, and the browser signs in with that token. Every SDK function then works
as normal, including saveData, search, file handling, and voice.
On your backend, using the Server SDK:
// behind your own session check
app.post('/api/bp-token', requireMyAuth, async (req, res) => {
const { customToken } = await bp.createUserToken({
externalId: req.user.id, // your own user id
email: req.user.email // a label for your dashboard
});
res.json({ customToken });
});
In the browser, after your own login succeeds:
Backprompter.setAppKey('YOUR_APP_KEY');
await Backprompter.signInWithCustomToken(customToken);
await Backprompter.saveData('preferences', { theme: 'dark' });
await Backprompter.run({ agentId: 'analyst', userPrompt: 'Hello' });
signInWithExternalAuth wraps the fetch for you.
await Backprompter.signInWithExternalAuth(
() => fetch('/api/bp-token', { credentials: 'include' }).then((r) => r.json())
);
Backprompter derives a storage id from your App Key and your external id. The same external id always returns to the same data. You cannot choose the id.
The session survives a page reload, because the auth library keeps a refresh token in the
browser. Your own logout must therefore call Backprompter.signOut().
Password setup and reset
Accounts created from the dashboard, and end users who ask to reset, arrive on a link carrying a one-time code.
https://your-page.example.com/?mode=resetPassword&oobCode=…&tenantId=…
The hosted chat handles this already. You need the code below only if you point the app at your own page, under Users, User Authentication, then Use a custom password setup page.
const info = Backprompter.readPasswordSetup(); // null if this is not such a link
if (info) {
// Verify first. An expired or used link should say so before
// somebody types a password.
const email = await Backprompter.verifyPasswordSetup(info.oobCode, info.tenantId);
// …show your form, then:
await Backprompter.completePasswordSetup(info.oobCode, newPassword, info.tenantId);
await Backprompter.signIn(email, newPassword);
}
For a "forgot my password" link of your own:
await Backprompter.requestPasswordReset('person@example.com');
This always resolves the same way, and it never reveals whether the address has an account. The email carries your app name and points at whichever page you configured.
Run an agent
const result = await Backprompter.run({
userPrompt: 'Summarize my last order.',
// agentId: 'MyAgent', // omit to auto-route to the best agent
conversationHistory: [
// { role: 'user', content: '…' }, { role: 'assistant', content: '…' },
],
profileString: '', // optional user-profile context
// fileInputs: [], // attachments, see below
// urlInputs: [], // URLs to attach as context
// contextLimit: 120000, // characters of history before auto-summarization
// maxTokens: 1000,
// ragCharBudget: 20000,
});
result.response; // the agent's text reply
result.agentId; // which agent handled it
result.artifactType; // 'chat' | 'markdown' | 'html' | 'charts' | 'javascript'
Attachments
Inside fileInputs, the key you use decides how the attachment is sent.
| Key | Sent as |
|---|---|
url, dataUrl, base64 | image |
file_url, file_data | document |
Backprompter infers the real type wherever it can. It reads an explicit mimeType,
the data-URL prefix, the file extension, or the leading bytes of base64. It falls back to the
key only when the type cannot be determined. A PDF passed as
{ url: '…/report.pdf' } is therefore still treated as a document. Pass
mimeType when you have it and neither guess is available.
await Backprompter.run({
userPrompt: 'What are the Q3 totals?',
fileInputs: [
{ file_url: 'https://example.com/q3.pdf', filename: 'q3.pdf' }, // document
{ url: 'https://example.com/chart.png' }, // image
],
});
Document support varies by model. OpenAI models accept spreadsheets. Anthropic models accept PDF only, and they reject other types outright. Choose a model that handles what you send.
Image links written in the prompt text are attached automatically, up to four per message. A
pasted https://…/photo.jpg therefore reaches a vision model without appearing in
fileInputs. Other links stay as text for the agent web-fetch tool, which returns
page text and cannot see images.
Letting the browser read local files
runWithFiles takes File objects straight from an
<input type="file">. It extracts text from PDFs and Office documents in the
browser, and it sends images as attachments.
const result = await Backprompter.runWithFiles({
userPrompt: 'Summarize these.',
promptFiles: Array.from(document.getElementById('files').files),
});
This requires the full bundle.
Voice
const ws = await Backprompter.connectVoice({
appId: 'YOUR_APP_ID',
conversationHistory: [],
onMessage: (msg) => {
if (msg?.type === 'response.audio_transcript.delta') console.log(msg.delta || '');
},
onError: (err) => console.error(err),
onClose: () => console.log('closed'),
});
// ws.send(JSON.stringify({ type: 'cancel_response' }));
Rendering agent artifacts
const html = Backprompter.renderArtifactHtml(result.response, { markdown: true, math: true });
Backprompter.renderArtifactElement(document.getElementById('pane'), result.response, {
markdown: true, math: true,
});
Showing sources
When an agent answers from your indexed documents, the run returns the passages it was given. This adds a Show sources button under that answer; it does nothing when the answer used no retrieval, so it is safe to call for every message.
const extra = typeof result.additional_response === 'string'
? JSON.parse(result.additional_response)
: (result.additional_response || {});
Backprompter.attachSourcesButton(bubbleEl, extra.rag?.docs, result.response);
Clicking it fills #artifactPaneContent with each passage: which document it
came from, where in it, and the text with the indexing noise removed. One cheap model call
then marks the sentences the answer rests on — it points at sentences by number and never
writes any text, so what is shown is always exactly what your document says. That call is
billed like any other agent call, which is why it happens on click.
Backprompter.showSources(docs, answerText); // open the panel yourself
Backprompter.getSources(docs); // [{ name, location, collection, text }]
await Backprompter.markSourceSentences({ answer, passages });
In a chat deployed from the builder, tick Show retrieved sources in the deploy dialog instead — no code needed.
Conversation history
await Backprompter.saveConversation(conversationId, messages, { title: 'My chat' });
const convo = await Backprompter.getConversation(conversationId); // .messages, .metadata
const list = await Backprompter.listConversations(); // .conversations[]
await Backprompter.deleteConversation(conversationId);
Artifacts
Artifacts are stored with vector embeddings, so search can find them.
await Backprompter.storeArtifacts([
{ name: 'notes.md', content: '# Notes…', summary: 'Project notes', sourceType: 'text' },
]);
const arts = await Backprompter.listArtifacts(); // .artifacts[]
const art = await Backprompter.getArtifact(docId); // .content, .name, …
await Backprompter.deleteArtifact(docId);
Custom data
Schemaless JSON documents with no embeddings. Use these for preferences and profiles.
await Backprompter.saveData('preferences', { theme: 'dark', language: 'en' });
const { data } = await Backprompter.getData('preferences');
await Backprompter.deleteData('preferences');
Vector search
const result = await Backprompter.search('notes from last week', {
searchIn: ['conversations', 'artifacts'],
k: 5,
});
Paid access
If you charge your end users, payment goes to your own Stripe account.
const { active } = await Backprompter.getEntitlements();
if (!active) {
const { url } = await Backprompter.createCheckoutSession({
successUrl: location.href + '?paid=1',
cancelUrl: location.href,
});
location.href = url;
}
Backprompter gates a run only when you configure Stripe on the app. If you collect money yourself, leave the payment settings empty. No gate then applies to anyone.