Quick Start
1. Set up your backend
// server.ts (Express example)import express from 'express';import { OpendoorClient } from '@opendoor/partner-sdk-server-js-core';
const app = express();app.use(express.json());
const opendoor = new OpendoorClient({ apiKey: process.env.OPENDOOR_API_KEY!,});
// Proxy routes - the browser SDK calls these endpointsapp.post('/api/opendoor/v1/addresses/suggestions', async (req, res) => { const result = await opendoor.getAddressSuggestions(req.body.query); res.json(result);});
app.post('/api/opendoor/v1/address/unit-check', async (req, res) => { const result = await opendoor.checkAddressUnit(req.body); res.json(result);});
app.post('/api/opendoor/v1/offer/create', async (req, res) => { const result = await opendoor.createOffer(req.body); res.json(result);});
app.post('/api/opendoor/v1/offer/update', async (req, res) => { const { offerId, ...data } = req.body; const result = await opendoor.updateOffer(offerId, data); res.json(result);});
app.post('/api/opendoor/v1/offer', async (req, res) => { const result = await opendoor.getOffer(req.body.offerId); res.json(result);});
app.post('/api/opendoor/v1/offer/pricing', async (req, res) => { const result = await opendoor.getOfferWithSellDirectPricing(req.body.offerId); res.json(result);});
app.post('/api/opendoor/v1/homeDetail', async (req, res) => { const result = await opendoor.getHomeDetail(req.body); res.json(result);});
app.post('/api/opendoor/v1/homebuilders', async (req, res) => { const result = await opendoor.getHomebuilders(); res.json(result);});
app.listen(3002);2. Add the frontend components
The browser client points at your backend, not at Opendoor. All requests go through the proxy you set up in step 1.
import { useState } from 'react';import { OpendoorProvider, AddressEntry, AddressUnitConfirmation, QualificationQuestions,} from '@opendoor/partner-sdk-client-react';import { OpendoorClient } from '@opendoor/partner-sdk-client-js-core';import type { Address } from '@opendoor/partner-sdk-client-js-core';
const client = new OpendoorClient({ baseURL: '/api/opendoor/v1' });
function App() { const [offerId, setOfferId] = useState<string | null>(null); const [address, setAddress] = useState<Address | null>(null); const [needsUnit, setNeedsUnit] = useState(false);
const handleAddressSelect = async (addr: Address) => { setAddress(addr); // Check if the address needs a unit number const { requiresUnitNumber } = await client.checkAddressUnit(addr); if (requiresUnitNumber) { setNeedsUnit(true); } else { await createOffer(addr); } };
const createOffer = async (addr: Address) => { const offer = await client.createOffer({ address: addr, salesAssociateName: 'Jane Smith', // optional salesAssociateEmail: 'jane@partner.com', // optional }); setOfferId(offer.opendoorOfferRequestId); };
return ( <OpendoorProvider client={client}> {offerId && address ? ( <QualificationQuestions offerId={offerId} address={address} salesAssociateName="Jane Smith" salesAssociateEmail="jane@partner.com" appearance={{ theme: 'minimal' }} onSubmit={async (answers) => { await client.updateOffer(offerId, answers); }} /> ) : ( <> <AddressEntry onAddressSelect={handleAddressSelect} /> {needsUnit && address && ( <AddressUnitConfirmation address={address} onConfirm={async (confirmed) => { setNeedsUnit(false); setAddress(confirmed); await createOffer(confirmed); }} onEdit={() => { setNeedsUnit(false); setAddress(null); }} /> )} </> )} </OpendoorProvider> );}<script setup lang="ts">import { ref } from 'vue';import { OpendoorProvider, AddressEntry, AddressUnitConfirmation, OpendoorClient,} from '@opendoor/partner-sdk-client-vue';import '@opendoor/partner-sdk-client-vue/dist/style.css';import type { Address } from '@opendoor/partner-sdk-client-js-core';
const client = new OpendoorClient({ baseURL: '/api/opendoor/v1' });
const offerId = ref<string | null>(null);const address = ref<Address | null>(null);const needsUnit = ref(false);
const createOffer = async (addr: Address) => { const offer = await client.createOffer({ address: addr, salesAssociateName: 'Jane Smith', // optional salesAssociateEmail: 'jane@partner.com', // optional }); offerId.value = offer.opendoorOfferRequestId;};
const handleAddressSelect = async (addr: Address) => { address.value = addr; // Check if the address needs a unit number const { requiresUnitNumber } = await client.checkAddressUnit(addr); if (requiresUnitNumber) { needsUnit.value = true; } else { await createOffer(addr); }};
const handleUnitConfirm = async (confirmed: Address) => { needsUnit.value = false; address.value = confirmed; await createOffer(confirmed);};
const handleEdit = () => { needsUnit.value = false; address.value = null;};</script>
<template> <OpendoorProvider :client="client"> <AddressEntry @address-select="handleAddressSelect" /> <AddressUnitConfirmation v-if="needsUnit && address" :address="address" @confirm="handleUnitConfirm" @edit="handleEdit" /> </OpendoorProvider></template>Note: The Vue SDK does not include
QualificationQuestions(it is React-only for the instant offer flow). UseDtcOnboardingFlowfor the traditional flow, or build a custom UI withclient-js-core.
3. That’s it
The SDK provides two questionnaire flows:
DTC Onboarding Flow (React & Vue) — 17-page one-question-per-page questionnaire matching the DTC flow on opendoor.com. Available in both frameworks via DtcOnboardingFlow.
Qualification Questions (React only) — 5-page multi-field form for the instant offer flow. Available via QualificationQuestions.
Both flows share the same address entry components:
AddressEntry— address suggestions, validation, and selectionAddressUnitConfirmation— collects unit/apt/floor when needed (see guide)
When the user completes a questionnaire, your onSubmit callback receives all answers as a flat object with dot-notation keys. Call client.updateOffer() to submit them — the server SDK automatically maps dot-notation keys to the GraphQL schema.
The API key never leaves your server. The browser SDK only talks to your backend at /api/opendoor/v1/*.