DocumentationAccounts with 2FA
How to connect an account that uses two-factor authentication
Connect a social account that asks for a second factor at sign-in. When you store the account's authenticator secret, BuzzTake makes the 2FA codes itself and can sign the account in again later with no one present.
You need a BuzzTake API key, your workspace id, and the account's username, password and authenticator secret.
Before you start
Create an API key in the web app: open Developers and use the "API keys" form. Your workspace id is the ws value in the web app's address bar, or the id of a workspace in GET /v1/agency/workspaces. The examples read both from the environment.
export BUZZTAKE_API_KEY="btk1_..."
export WORKSPACE_ID="0199..."1. Find the authenticator secret
An authenticator app makes its codes from a secret key that the platform shows once, when you set up the app. The QR code holds the same key. BuzzTake needs the key as text.
- Open the platform's security settings and choose to add an authentication app.
- When the platform shows the QR code, choose the option to enter the key by hand. The platform then shows the key as text.
- Copy the key. Add it to your own authenticator app too, so you keep access.
- Finish the setup on the platform with a code from your app. A key from a setup you did not finish makes codes the platform refuses.
The key uses the letters A to Z and the digits 2 to 7. BuzzTake removes spaces and hyphens and accepts lowercase, so you can paste it as the platform shows it. It must be 16 to 128 characters long. If you have an otpauth:// link instead, send only the value after secret=.
2. Add the account with the secret
POST/v1/workspaces/{workspace_id}/accounts
Send the username, the password and the secret in connect. BuzzTake stores all three encrypted and never returns them; the account shows only has_credentials: true.
curl
curl https://api.buzztake.com/v1/workspaces/$WORKSPACE_ID/accounts \
-H "Authorization: Bearer $BUZZTAKE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"platform": "instagram",
"label": "Brand Instagram",
"connect": {
"method": "password",
"username": "acme_demo",
"password": "your-password",
"totp_secret": "JBSW Y3DP EHPK 3PXP"
}
}'JavaScript
const res = await fetch(
`https://api.buzztake.com/v1/workspaces/${process.env.WORKSPACE_ID}/accounts`,
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BUZZTAKE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
platform: "instagram",
label: "Brand Instagram",
connect: {
method: "password",
username: "acme_demo",
password: process.env.ACCOUNT_PASSWORD,
totp_secret: process.env.ACCOUNT_TOTP_SECRET,
},
}),
},
);
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
const { account, action } = await res.json();
console.log(account.id, action.id, action.status);Python
import os
import requests
resp = requests.post(
f"https://api.buzztake.com/v1/workspaces/{os.environ['WORKSPACE_ID']}/accounts",
headers={"Authorization": f"Bearer {os.environ['BUZZTAKE_API_KEY']}"},
json={
"platform": "instagram",
"label": "Brand Instagram",
"connect": {
"method": "password",
"username": "acme_demo",
"password": os.environ["ACCOUNT_PASSWORD"],
"totp_secret": os.environ["ACCOUNT_TOTP_SECRET"],
},
},
timeout=30,
)
if not resp.ok:
raise SystemExit(f"{resp.status_code}: {resp.text}")
body = resp.json()
print(body["account"]["id"], body["action"]["id"], body["action"]["status"]){
"account": { "id": "0199a1b2-...", "platform": "instagram", "status": "connecting", "has_credentials": true },
"action": { "id": "0199c3d4-...", "type": "connect", "status": "queued", "credits": 25 }
}The answer holds the new account, with status connecting, and a connect action that signs in. Each sign-in attempt costs 25 credits, charged when it starts. A failed attempt is not refunded.
In the web app, the same form is under Accounts, "Add account". Pick the platform, choose "Username and password", and paste the key into "Authenticator secret (optional)". With an MCP client, call connect_account with platform, username, password and totp_secret.
3. Wait for the sign-in
Read the connect action with wait. The request holds for up to that many seconds (at most 120) and answers as soon as the action stops running or needs input.
curl "https://api.buzztake.com/v1/actions/$ACTION_ID?wait=60" \
-H "Authorization: Bearer $BUZZTAKE_API_KEY"succeeded: the account isactiveand ready for actions.needs_input: the platform asked for something the secret cannot answer. Go to step 4.failed: readerror.code. See Troubleshooting below.queuedorrunning: the wait ran out first. Ask again.
4. Answer an email or SMS code
When the platform asks for a code it sent you, the action stops at needs_input with a challenge. Answer before expires_at, which is at most 10 minutes away.
{
"id": "0199c3d4-...",
"type": "connect",
"status": "needs_input",
"challenge": {
"kind": "email_code",
"message": "Enter the code the platform sent by email.",
"expires_at": "2026-10-30T09:10:00Z",
"sign_in_url": "https://buzztake.com/connect/bts_..."
}
}curl
curl -X POST https://api.buzztake.com/v1/actions/$ACTION_ID/answer \
-H "Authorization: Bearer $BUZZTAKE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "answer": "482913" }'JavaScript
const res = await fetch(
`https://api.buzztake.com/v1/actions/${actionId}/answer`,
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BUZZTAKE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ answer: code }),
},
);
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
console.log((await res.json()).status); // "running"Python
resp = requests.post(
f"https://api.buzztake.com/v1/actions/{action_id}/answer",
headers={"Authorization": f"Bearer {os.environ['BUZZTAKE_API_KEY']}"},
json={"answer": code},
timeout=30,
)
if not resp.ok:
raise SystemExit(f"{resp.status_code}: {resp.text}")
print(resp.json()["status"]) # "running"The action goes back to running. Wait for it again as in step 3: the platform can ask for a second check. Over MCP, the tool result carries the same challenge; pass the code to answer_challenge with the action_id.
| Kind | When | Answer |
|---|---|---|
two_factor_code | The platform asks for an authenticator code and no secret is stored. | Send the current 6-digit code from your authenticator app. |
email_code | The platform sent a code by email. | Send the code from the email. |
sms_code | The platform sent a code by text message. Telegram login codes arrive this way. | Send the code from the message. |
device_approval | The platform asks you to approve the sign-in on a device that is already signed in. | Approve it there, then send approved. The web app's "I approved it" button sends the same. |
captcha | The platform shows a puzzle. | Open sign_in_url and solve it in the live browser. |
sign_in_url opens the account's live browser with no BuzzTake login until the challenge expires, so you can also send it to the person who receives the codes.
Troubleshooting
- 422 invalid_input
- "
totp_secretmust be the base32 secret from the authenticator setup (letters A-Z and digits 2-7)." You sent a code, a recovery code or anotpauth://link. Send the setup key. - 402 insufficient_credits
- The balance cannot pay for one sign-in attempt (25 credits). Nothing was created.
- login_failed
- The platform refused the password or a code. Fix the stored login with
PATCH /v1/accounts/{id}and a newcredentialsobject, then sign in again withPOST /v1/accounts/{id}/connect. - challenge_timeout
- Nobody answered before
expires_at. Sign in again and answer in time. - account_blocked
- The platform shows a checkpoint. Create a sign-in link and clear it in the live browser.
See also
- Connect methods
- Sign-in challenges
- How to connect a social account with exported cookies
- How to let a client connect an account without sharing a password
- How to check and fix a signed-out account
To run this on your own accounts, create a BuzzTake account, then add an account and an API key.