curl --request POST \
--url https://api.bachs.io/v1/accounts/{account_id}/account-links \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"type": "onboarding",
"refresh_url": "https://adastores.example/connect/refresh",
"return_url": "https://adastores.example/connect/return"
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
type: 'onboarding',
refresh_url: 'https://adastores.example/connect/refresh',
return_url: 'https://adastores.example/connect/return'
})
};
fetch('https://api.bachs.io/v1/accounts/{account_id}/account-links', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.bachs.io/v1/accounts/{account_id}/account-links"
payload = {
"type": "onboarding",
"refresh_url": "https://adastores.example/connect/refresh",
"return_url": "https://adastores.example/connect/return"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.bachs.io/v1/accounts/{account_id}/account-links"
payload := strings.NewReader("{\n \"type\": \"onboarding\",\n \"refresh_url\": \"https://adastores.example/connect/refresh\",\n \"return_url\": \"https://adastores.example/connect/return\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}{
"id": "alnk_3b7e12c9d4a05f68b1c2",
"object": "connected_account_link",
"account": "acct_3Wq8ZfT1yHnJ5sVe",
"type": "onboarding",
"created": "2026-08-07T11:04:22.518Z",
"expires_at": "2026-09-06T11:04:22.518Z",
"url": "https://connect.bachs.io/setup/c/acct_3Wq8ZfT1yHnJ5sVe/al_kQ2v8nS1xJd0pR7mLtY4wZ6aHb3cFg9e",
"previous_link_superseded": false
}{
"detail": "Invalid request parameters",
"error_code": "VALIDATION_ERROR",
"errors": [
{
"field": "amount",
"message": "Amount must be a positive decimal string",
"type": "value_error"
}
]
}{
"detail": "Invalid API key",
"error_code": "UNAUTHORIZED"
}{
"detail": "API key does not have permission for this operation",
"error_code": "FORBIDDEN"
}{
"detail": "Resource not found",
"error_code": "NOT_FOUND"
}Create an account link
Issue a hosted link that walks an account through its outstanding requirements. Creating a link invalidates any outstanding active link of the same type for that account, so create one at the moment you redirect rather than on every page render. Requires an active connect capability on your own platform. See Onboarding.
curl --request POST \
--url https://api.bachs.io/v1/accounts/{account_id}/account-links \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"type": "onboarding",
"refresh_url": "https://adastores.example/connect/refresh",
"return_url": "https://adastores.example/connect/return"
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
type: 'onboarding',
refresh_url: 'https://adastores.example/connect/refresh',
return_url: 'https://adastores.example/connect/return'
})
};
fetch('https://api.bachs.io/v1/accounts/{account_id}/account-links', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.bachs.io/v1/accounts/{account_id}/account-links"
payload = {
"type": "onboarding",
"refresh_url": "https://adastores.example/connect/refresh",
"return_url": "https://adastores.example/connect/return"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.bachs.io/v1/accounts/{account_id}/account-links"
payload := strings.NewReader("{\n \"type\": \"onboarding\",\n \"refresh_url\": \"https://adastores.example/connect/refresh\",\n \"return_url\": \"https://adastores.example/connect/return\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}{
"id": "alnk_3b7e12c9d4a05f68b1c2",
"object": "connected_account_link",
"account": "acct_3Wq8ZfT1yHnJ5sVe",
"type": "onboarding",
"created": "2026-08-07T11:04:22.518Z",
"expires_at": "2026-09-06T11:04:22.518Z",
"url": "https://connect.bachs.io/setup/c/acct_3Wq8ZfT1yHnJ5sVe/al_kQ2v8nS1xJd0pR7mLtY4wZ6aHb3cFg9e",
"previous_link_superseded": false
}{
"detail": "Invalid request parameters",
"error_code": "VALIDATION_ERROR",
"errors": [
{
"field": "amount",
"message": "Amount must be a positive decimal string",
"type": "value_error"
}
]
}{
"detail": "Invalid API key",
"error_code": "UNAUTHORIZED"
}{
"detail": "API key does not have permission for this operation",
"error_code": "FORBIDDEN"
}{
"detail": "Resource not found",
"error_code": "NOT_FOUND"
}Authorizations
Bearer token authentication. Pass your API key as Authorization: Bearer sk_.... See Authentication for keys, scopes, and sandbox vs production.
Path Parameters
The account to act on. It must be one of your own accounts; any other ID returns 404 so the response never confirms that an unrelated account exists.
Body
What the account holder is being sent to do. onboarding: collect everything the account still owes for the first time. update: revisit information already collected, which requires the account to have requirements already and otherwise fails with 400 CONNECTED_ACCOUNT_REQUIREMENTS_NOT_FOUND.
onboarding, update "onboarding"
Where the account holder is sent when the link is no longer usable, for example after it expired. Issue a fresh link from the page you point at, because the original URL cannot be revived.
"https://adastores.example/connect/refresh"
Where the account holder is sent when they finish or abandon the flow. Arriving here is not proof that onboarding completed, so confirm from the account.updated event rather than from the redirect.
"https://adastores.example/connect/return"
Options carried through to the hosted flow and handed back unchanged when the link is opened. Omit it unless you were given specific keys to send.
Response
Account link created. url is returned only here and cannot be read back.
Unique identifier for the account link.
"alnk_3b7e12c9d4a05f68b1c2"
Always connected_account_link, so a mixed webhook or log stream can be routed on type.
connected_account_link "connected_account_link"
The account this link onboards.
"acct_3Wq8ZfT1yHnJ5sVe"
What the link was issued for, echoing the type you sent. onboarding: the account holder is walked through everything the account still owes, for the first time. update: the account holder revisits information already collected, which only works once the account has requirements and otherwise fails with 400 CONNECTED_ACCOUNT_REQUIREMENTS_NOT_FOUND.
onboarding, update "onboarding"
When the link was issued, ISO 8601 in UTC.
"2026-08-07T11:04:22.518Z"
When the link stops working, ISO 8601 in UTC. After this the account holder lands on your refresh_url instead. Read this value rather than assuming a fixed lifetime.
"2026-09-06T11:04:22.518Z"
Send the account holder here. The URL carries a single-use credential, so deliver it over a channel you trust and keep it out of logs and analytics. It is returned only on this response and cannot be read back.
"https://connect.bachs.io/setup/c/acct_3Wq8ZfT1yHnJ5sVe/al_kQ2v8nS1xJd0pR7mLtY4wZ6aHb3cFg9e"
true when issuing this link invalidated an outstanding active link of the same type for the account. Generating a link on every page render keeps invalidating the one you already sent, so create a link when you are about to redirect and not before.
false

