Give a user a Configure account from inside your app
Invite-only
Configure enables this flow per partner. A secret key on its own does not reach it. An uninvited key gets 404 from the mint endpoint. An invited partner whose installation does not list this flow gets a 400 that says so. If you have not been given credentials for it directly, talk to Configure first.
The import-only flow carries a user's context into your app without a Configure account. This flow gives the user a real Configure account, bound to your own user id, in the same page. Use it when your agent will:
- use Configure's MCP for this user,
- mint connect links for Notion, Google Calendar and the rest, or
- carry the user's context into another product.
You keep your own user id. You pass the email you already verified. The user proves a phone number on Configure's page. Configure finds or creates the account behind that phone, attaches your email to it, links your id to it, and approves your agent for it. The import screen and the receipt follow.
What the user sees
One page, hosted by Configure, branded with your name and logo:
- Phone. The same phone screen every Configure sign-in uses, under a header with your name and logo.
- Code. The same six-cell code screen. The grant is stated under it. Verifying connects their Configure account to you.
- Import. The import screen shows the assistants you asked for, and the apps Gmail, Google Calendar, Google Drive, Notion and Outlook. On this flow, Continue works with nothing imported, because the account itself is the goal.
- Receipt, "You're all set", then a button back to you.
The same screens are pictured on What the hosted flow asks for. There is no permissions screen. Entering the code is the grant.
What you need
| A secret key | sk_..., the same key the import-only flow uses. Bound to your agent. |
| The flow enabled | Configure sets flows on your installation. Ask for linked_account. |
| A verified email per user | The email you already verified for this user. Send it at mint. You set a flag to say whether you verified it, and Configure believes you. |
| Registered return hosts | Same as the import-only flow. |
The email comes from you because you already made the user prove it. This flow does not make them prove it again to Configure.
The phone comes from Configure because phone is Configure's canonical identity ("same phone = same person"). It is how the same person's context finds them in every other product that uses Configure. A partner cannot assert a phone number. The user proves it.
1. Mint a session when the user clicks
bash
curl -X POST https://api.configure.dev/v1/flows/sessions \
-H "X-API-Key: $CONFIGURE_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"flow": "linked_account",
"partner_ref": "your-own-user-id",
"email": "dana@example.com",
"email_verified": true,
"sources": ["chatgpt", "claude"],
"return_url": "https://yourapp.example/welcome"
}'| Field | Required | Notes |
|---|---|---|
flow | yes | linked_account. Configure serves it as the connect flow (the import screen first, then the phone and the code) and reports flow: "connect", with the same account, email and phone behavior. Leave it out to get your installation's default, which is the connect flow wherever your installation has it. |
partner_ref | yes | Your opaque id for this user. Same rules as the import-only flow: not an email or phone, and rejected if it looks like one. |
email | yes | The user's email. Normalised to lower case. Dots and plus tags are kept. |
email_verified | no | true only if you verified it: a link they clicked, a code they entered, or an identity provider that asserts it. Defaults to false. |
sources | no | Any of chatgpt, claude, gemini, grok. Defaults to all four. |
required | no | What the user must import or connect before they can continue: any of your sources, and gmail, outlook, calendar, drive, notion. They show first, marked Required. An entry may name alternatives: `"gmail |
return_url | no | Where the receipt sends the user. https on a host registered for your account. |
json
{
"id": "d98ef4a7-4d7d-41a8-b4a5-8b818b20cd54",
"status": "created",
"livemode": true,
"partner_ref": "your-own-user-id",
"flow": "linked_account",
"steps": ["phone", "otp", "imports", "done"],
"sources": ["chatgpt", "claude"],
"imports": [],
"return_url": "https://yourapp.example/welcome",
"profile_ready": false,
"failure": null,
"account": { "phone_verified": false, "linked": false, "configure_user_id": null, "phone_verified_at": null, "phone": null },
"expires_at": "2026-09-17T09:15:00.000Z",
"url": "https://accounts.configure.dev/embed/auth?flow_handoff=cfgfh_rEfo5Fac...&agent=your-agent&flow=linked_account"
}Send the user to url. The link lasts 15 minutes and is single use, so mint it inside the click handler. steps is the screen list this session will walk. It never changes once minted. An installation with Configure's Gmail step turned on also lists "gmail". The page shows the same import screen either way.
Require an app or an assistant
When your product needs something, require it. If your product is built on their ChatGPT history, require ChatGPT. If you need a connected mailbox, require Gmail or Outlook. Pass required when you mint. Join alternatives with |. For example, gmail|outlook means either mailbox.
json
{
"partner_ref": "your-own-user-id",
"flow": "linked_account",
"email": "dana@example.com",
"sources": ["chatgpt", "claude"],
"required": ["gmail|outlook", "chatgpt"]
}Each required app and assistant moves to the top of its list and is marked Required in red. An entry made only of mailboxes, such as gmail|outlook, gets its own page instead, after the code and before the import screen. That page reads "Connect your apps and assistants to" your app, with Continue with Gmail and Continue with Outlook.
For an entry with alternatives, each alternative is marked. The line above Continue says one is enough, for example "Connect Gmail or Outlook to continue." The marks and the line disappear when the user completes one of them. Continue stays off until every entry is met. Everything else stays optional.
The rules:
- Values: any of the
sourcesyou passed (chatgpt,claude,gemini,grok), and the appsgmail,outlook,calendar,drive,notion. Anything else is a400that names what it did not accept. The same values apply to each alternative of an entry. - Account flows only (
linked_accountandconnect). The import-only flow refuses it. - It travels on the link you get back (
&required=gmail|outlook,chatgpt, URL-encoded). It shapes the screen. It is not a security control. When you poll, read what landed from the session (connections,imports).
The email is not echoed back, and it never appears in the handoff the browser can read. It reaches the page only behind the cookie the user's own gesture mints.
2. The user does the flow
You build nothing here. Configure's page sends the code, checks it, creates or finds the account, runs the imports and shows the receipt.
What happens on the code screen, in order, all inside Configure:
- The phone resolves to a Configure account through the same sign-in every hosted page uses. If an account exists for that phone, it is used. Otherwise one is created.
- Your
partner_refis linked to that account. Anything already held under your id for this user, such as an earlier import-only session, is merged in. - Your email is attached to the account. It is recorded as verified only if you said
email_verified: trueand no other Configure account has already proven that address. If another account has, your email is stored unverified and the event is audited. The flow continues either way. You are not told, for the same reasonPOST /v1/flows/identitydoes not tell you. - Your agent is approved for the account. Without this, a linked account answers
approval_requiredto your next profile read.
One case is refused: a partner_ref that was already linked to a different phone's account. An API call never joins two real accounts. The user sees a screen that explains this, and a button back to you. The session does not reach ready.
3. Poll the session
bash
curl https://api.configure.dev/v1/flows/sessions/$SESSION_ID \
-H "X-API-Key: $CONFIGURE_SECRET_KEY"json
{
"status": "ready",
"flow": "linked_account",
"account": {
"phone_verified": true,
"linked": true,
"configure_user_id": "efc419f4-797e-4e19-9615-70c7e2945444",
"phone_verified_at": "2026-09-17T09:03:12.410Z",
"phone": "+14155550123"
},
"imports": [{ "source": "chatgpt", "status": "completed", "memory_count": 5 }],
"profile_ready": true,
"failure": null
}The statuses are the same as in the import-only flow. The differences:
readyis reached when the user finishes the page, with or without imports. The receipt is an explicit step, so an account with nothing to import still completes.accounttells you when the phone was proven, and the id Configure knows this person by. Storeconfigure_user_idnext to your own user row. It is the same idGET /v1/flows/identity/:partner_refreturns.phoneis the number they proved, in E.164. You gave Configure the email, Configure got the phone, and each side passes back what the other does not have. Store it, and never ask them for a phone number yourself. It isnulluntil they prove one, so an abandoned session tells you nothing.
4. Read the profile with your own id
bash
curl https://api.configure.dev/v1/profile \
-H "X-API-Key: $CONFIGURE_SECRET_KEY" \
-H "X-User-Id: your-own-user-id"linked is now true. Everything the SDK does for a linked user works, including profile.commit, profile.mcpSession and profile.connect. The summary still lands a few seconds after ready. If it is empty, read again.
The same read tells you which tools the account has connected. integrations is keyed by connector, and connected is the only field you need:
json
{
"linked": true,
"integrations": {
"gmail": { "connected": true, "accountEmail": "dana@gmail.com" },
"calendar": { "connected": false }
},
"imports": { "chatgpt": { "memory_count": 5 } }
}5. Let them connect their tools from your own screen
This is the main reason to choose this flow over import-only. The user now has a Configure account bound to your partner_ref. Your receipt, settings page or agent can offer Gmail, Google Calendar, Google Drive and Notion without sending the person back through the hosted page. Ask Configure for a connect link, open it, and read the profile again when they come back.
bash
curl -X POST https://api.configure.dev/v1/profile/connect \
-H "X-API-Key: $CONFIGURE_SECRET_KEY" \
-H "X-User-Id: your-own-user-id" \
-H "Content-Type: application/json" \
-d '{ "app": "calendar" }'json
{
"status": "connect_app",
"connect_url": "https://mcp.configure.dev/connect/mcc_9k2...",
"connected": false,
"app": "calendar",
"expires_at": "2026-09-17T10:03:12.410Z"
}| Field | Notes |
|---|---|
app | One of gmail, calendar, drive, notion, sheets. Omit it for a link that manages every connection. |
status | connect_app for a linked user. authorization_required means this partner_ref has no linked account yet, and the link signs them in first. You see it only before the flow has finished. |
connected | Whether that app is already connected. The link then manages it rather than adding it. |
connect_url | Configure-hosted. Open it in a popup or a new tab. Never build one yourself, and never cache it across users. |
Three rules, the same as for the import link:
- Mint at the click. A link lasts an hour and is bound to this user and your agent. There is nothing to store.
- The profile is the proof, not the redirect. Poll
GET /v1/profileevery few seconds while their window is open, and stop whenintegrations.<app>.connectedturnstrueor the window closes. A person who abandons the consent screen never returns anywhere. - Only show the tools for a linked account. An import-only user has no account. A connect link for them is a sign-in, which promises something different from "connect Calendar". Decide with
account.linkedon the session, orlinkedon the profile.
Newly's onboarding is the reference. Its receipt lists what the user brought, then the four tools with a Connect button each. It marks a tool connected as soon as the profile says so. The whole integration is one endpoint on Newly's server, which calls the request above with the signed-in user's id, plus a profile read. The SDK does the same in one call: client.profile({ externalId }).connect({ app: "calendar" }).
What can go wrong
| You see | Meaning |
|---|---|
400 on flow naming your installation | The flow is not enabled for you. Email partners@configure.dev. |
400 on email | Missing, or not a deliverable address. |
400 email is only accepted with an account flow (linked_account, connect). | You sent email without flow: "linked_account" or flow: "connect". |
status: opened for a long time, account.phone_verified: false | The user has not passed the code. Treat "past expires_at" as abandonment. |
account.phone_verified: true, never ready | They left before the receipt. Their account and link are real, and the profile read works. |
| The user sees | Why |
|---|---|
| That code isn't right | Wrong code. Five attempts per number every 10 minutes, then a wait. |
| Too many attempts | The window is exhausted. They can resend after the countdown. |
| This account already has a phone | Their partner_ref was linked to another phone earlier. Contact Configure. |
Test mode
Use your sk_test_ key. Test sessions resolve against your test developer account and can never touch a live user. The reviewer phone number and code Configure gave you work on this page as on any other hosted page.