Continue With Configure
Use Continue with Configure when your app already has sign-in or account linking and wants Configure as an OAuth SSO provider. The flow authenticates the user on Configure's first-party account surface, returns an authorization code to your callback, and lets your backend make profile tool calls for that approved user.
For chat products, pair SSO with Inline UI Components. SSO proves who the user is; inline Configure lets the user manage profile permissions, connect tools, and tune personalization from inside the chat. Use Link-only only when the host product does not have sign-in or an OAuth provider row.
Create A Client
Create an OAuth SSO client for one of your developer-owned agents:
http
POST /v1/developer/oauth/clients
Authorization: Bearer <developer_token>
Content-Type: application/json
{
"agent": "your-agent",
"client_name": "Your App",
"redirect_uris": ["https://app.example.com/auth/configure/callback"],
"scope": "profile.read profile.search profile.remember profile.commit"
}The response includes a client_id and, for confidential web clients, a one-time client_secret. Store the secret server-side. Do not put it in browser JavaScript.
Use a developer dashboard bearer token for this request, not an sk_ API key. Today that token is issued when you sign in to the developer dashboard at https://configure.dev/login; until the OAuth client UI ships, advanced integrations can copy the configure_developer_token value from the dashboard session and send it as Authorization: Bearer <developer_token>.
json
{
"client_id": "oc_...",
"client_secret": "ocs_...",
"client_name": "Your App",
"agent": "your-agent",
"redirect_uris": ["https://app.example.com/auth/configure/callback"],
"scope": "profile.read profile.search profile.remember profile.commit",
"resource": "https://api.configure.dev"
}Add The Button
Add the hosted script and button beside your existing SSO options:
html
<script src="https://configure.dev/js/configure.js"></script>
<configure-sso-button
client-id="oc_..."
redirect-uri="https://app.example.com/auth/configure/callback"
scopes="profile.read profile.search profile.remember profile.commit"
width="100%">
</configure-sso-button>The button opens https://accounts.configure.dev/oauth/authorize in a popup by default, generates a PKCE challenge, and uses the grey Configure SVG brandmark on a white button with black text.
Default dimensions are 240px wide by 40px high, keeping the full "Continue with Configure" label visible while staying inside common OAuth button bounds. Google caps custom button width at 400px and recommends third-party sign-in buttons be approximately the same size and visual weight; Apple allows web button heights from 30px to 64px and widths from 130px to 375px or 100%.
Reference baselines: Google Identity button reference, Apple web button guidance, and Microsoft sign-in branding.
Choose the Configure button size by matching the OAuth provider buttons already in your product. Measure the rendered Google, Apple, Microsoft, or other SSO rows and use the Configure size that keeps every button in the same group the same visual height:
| Existing provider row | Configure size |
|---|---|
32px compact rows | size="compact" |
40px standard rows | omit size or use size="standard" |
48px large rows | size="large" |
Use width="100%" only inside a fixed-width host row where every provider button shares the same visual width and height. Good host rows are usually 240px to 400px wide. The default brandmark renders at 18px square and compact mode renders it at 16px square.
Use launch-mode="iframe" when you want the Continue with Configure flow to stay in a closeable in-page modal:
html
<configure-sso-button
client-id="oc_..."
redirect-uri="https://app.example.com/auth/configure/callback"
scopes="profile.read profile.search profile.remember profile.commit"
launch-mode="iframe"
width="100%">
</configure-sso-button>The iframe mode still uses the OAuth authorization endpoint and PKCE. Configure allows the authorization page to be framed only by the registered redirect origin for that client. Your callback page must also be frameable by your own app and call Configure.completeSso() after your backend exchanges the code.
Use launch-mode="redirect" when your environment blocks popups and iframes.
Programmatic equivalent:
js
Configure.ssoButton({
el: "#configure-sso",
clientId: "oc_...",
redirectUri: "https://app.example.com/auth/configure/callback",
scopes: ["profile.read", "profile.search", "profile.remember", "profile.commit"],
launchMode: "iframe",
width: "100%",
});For programmatic mounts, use a plain container such as <div id="configure-sso"></div>. Do not put data-configure-sso on that container. The data-configure-sso attribute is reserved for declarative auto-mounting and expects client-id or data-client-id on the same element:
html
<div
data-configure-sso
data-client-id="oc_..."
data-redirect-uri="https://app.example.com/auth/configure/callback"
data-scopes="profile.read profile.search profile.remember profile.commit">
</div>If your backend already generated state, code_challenge, and the full authorization URL, pass authorizationUrl to the helper instead. For iframe launches, include frame_origin=<your app origin> in the authorization URL. Keep the matching code_verifier in your server session.
Handle The Callback
The callback page receives code and state. Exchange the code on your backend, store Configure tokens server-side, then notify the opener and close the popup.
The drop-in helper stores the PKCE verifier under your app origin in both localStorage and sessionStorage with a 30-minute TTL, so redirect mode can survive a full cross-origin round trip. Configure.getSsoPkce(state) checks both stores. Configure.completeSso({ state, ... }) clears the verifier after a successful exchange; call Configure.clearSsoPkce(state) yourself if you complete the flow without completeSso().
html
<script src="https://configure.dev/js/configure.js"></script>
<script>
async function finishConfigureSso() {
const params = new URLSearchParams(window.location.search);
const code = params.get("code");
const state = params.get("state");
const error = params.get("error");
if (error) {
Configure.completeSso({ error, payload: { message: error } });
return;
}
const pkce = Configure.getSsoPkce(state);
if (!pkce || !pkce.codeVerifier) {
Configure.completeSso({
error: "pkce_missing",
payload: { message: "Configure sign-in expired. Please try again." },
});
return;
}
const res = await fetch("/api/configure/oauth/callback", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
code,
state,
codeVerifier: pkce.codeVerifier,
redirectUri: window.location.origin + window.location.pathname,
resource: "https://api.configure.dev",
}),
});
if (!res.ok) {
Configure.completeSso({
error: "callback_failed",
payload: { message: "Configure sign-in failed." },
});
return;
}
const result = await res.json();
Configure.completeSso({ state, payload: result });
}
finishConfigureSso();
</script>Configure.completeSso() notifies either the popup opener or the parent iframe, then closes the popup when one exists. In iframe mode the modal closes from the parent page after it receives the callback message.
Server exchange example:
ts
app.post("/api/configure/oauth/callback", async (req, res) => {
const { code, codeVerifier, redirectUri, resource } = req.body;
const tokenRes = await fetch("https://api.configure.dev/oauth/token", {
method: "POST",
headers: {
"Authorization": "Basic " + Buffer.from(
process.env.CONFIGURE_OAUTH_CLIENT_ID + ":" +
process.env.CONFIGURE_OAUTH_CLIENT_SECRET
).toString("base64"),
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
grant_type: "authorization_code",
code,
redirect_uri: redirectUri,
client_id: process.env.CONFIGURE_OAUTH_CLIENT_ID!,
code_verifier: codeVerifier,
resource: resource || "https://api.configure.dev",
}),
});
const tokens = await tokenRes.json();
if (!tokenRes.ok) {
return res.status(400).json({ error: "configure_oauth_failed" });
}
req.session.configureAccessToken = tokens.access_token;
req.session.configureRefreshToken = tokens.refresh_token;
res.json({ connected: true });
});Validate state in your app before exchanging the code. The example uses browser-generated PKCE for the drop-in path. Apps that already have a session-backed OAuth helper can generate their own state and codeVerifier, persist the verifier in durable app storage, and pass both into Configure.ssoButton({ state, codeVerifier, ... }).
Use Profile Tools
After exchange, use the Configure access token from your server session with the normal SDK runtime:
ts
import { Configure, toOpenAIFunctions } from "configure";
const configure = new Configure({
apiKey: process.env.CONFIGURE_API_KEY,
agent: process.env.CONFIGURE_AGENT,
});
async function toolsForUser(req) {
const profile = configure.profile({
token: req.session.configureAccessToken,
});
return {
profile,
tools: toOpenAIFunctions(profile.tools()),
};
}Route only Configure-prefixed tool calls back to Configure:
ts
const result = call.function.name.startsWith("configure_")
? await profile.executeTool({
name: call.function.name,
arguments: JSON.parse(call.function.arguments || "{}"),
})
: await executeYourTool(call);The OAuth access token resolves the Configure user and acting agent server-side. Do not send user IDs, CFS paths, app display names, or browser-supplied agent names to choose the profile namespace.
Add Inline Configure After SSO
After Continue with Configure succeeds, add Configure inside the chat surface. The user should not need to authenticate again. Inline Configure becomes the place to adjust permissions, connect Gmail/Calendar/Drive/Notion/Sheets, and toggle personalization for the current agent.
html
<script src="https://configure.dev/js/configure.js"></script>
<div id="configure-entry"></div>
<script>
Configure.personalizationButton({
el: "#configure-entry",
publishableKey: "pk_...",
agent: "your-agent",
agentName: "Your Agent",
personalizationLabel: "Manage Configure",
variant: "integration",
onEvent(event) {
if (event.type === "configure:linked") {
fetch("/api/configure/session", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
token: event.payload.token,
userId: event.payload.userId,
}),
});
}
},
});
</script>Keep OAuth access and refresh tokens on the backend. Hosted Configure UI can reuse the Configure-origin session created during SSO. Pass a browser-safe Configure handoff token or userId only when your backend intentionally returns one for hosted UI; never pass OAuth access or refresh tokens to browser JavaScript.
If the user has not completed SSO, the same inline surface can fall back to Configure Link and emit configure:linked.
Scopes
Default web SSO scopes are profile-only:
| Scope | Allows |
|---|---|
profile.read | GET /v1/profile and configure_profile_read |
profile.search | GET /v1/profile/search and configure_profile_search |
profile.remember | POST /v1/profile/remember and configure_profile_remember |
profile.commit | POST /v1/profile/commit and profile.commit() |
Connector and action tools still require the user to connect the underlying tool and the host runtime to opt into those tools. Keep refresh tokens, access tokens, and connector credentials on the backend.
Connected State
For returning users, render the button with a connected state after your backend verifies the server-side Configure session:
html
<configure-sso-button
client-id="oc_..."
redirect-uri="https://app.example.com/auth/configure/callback"
connected="true"
connected-label="Configure connected"
account-popover="inline">
</configure-sso-button>Clicking a connected button opens a small account popover and can be closed without restarting auth. The popover does not expose tokens.