Move a Line Between Workspaces
curl --request POST \
--url https://app.tuco.ai/api/agency/lines \
--header 'Authorization: Bearer <token>'import requests
url = "https://app.tuco.ai/api/agency/lines"
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, headers=headers)
print(response.text)const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
fetch('https://app.tuco.ai/api/agency/lines', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.tuco.ai/api/agency/lines",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://app.tuco.ai/api/agency/lines"
req, _ := http.NewRequest("POST", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://app.tuco.ai/api/agency/lines")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.tuco.ai/api/agency/lines")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_bodyAgency
Move a Line Between Workspaces
Move an iMessage/SMS/email line from one workspace in your agency to another. Cancels anything still queued on the line.
POST
/
api
/
agency
/
lines
Move a Line Between Workspaces
curl --request POST \
--url https://app.tuco.ai/api/agency/lines \
--header 'Authorization: Bearer <token>'import requests
url = "https://app.tuco.ai/api/agency/lines"
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, headers=headers)
print(response.text)const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
fetch('https://app.tuco.ai/api/agency/lines', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.tuco.ai/api/agency/lines",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://app.tuco.ai/api/agency/lines"
req, _ := http.NewRequest("POST", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://app.tuco.ai/api/agency/lines")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.tuco.ai/api/agency/lines")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_bodyMoving a line cancels every message still waiting to go out on it — status
queued, pending or scheduled (scheduled is the rescheduled/retry state).
Send a preview first to see how many that is.Endpoint
- Method:
POST - Path:
/api/agency/lines - Auth:
Authorization: Bearer tucoagency_xxxxxxxxxxxxx(agency key)
Body
| Field | Type | Required | Notes |
|---|---|---|---|
action | string | yes | "transfer" |
lineId | string | yes | From List Agency Lines |
targetWorkspaceId | string | yes | A clerkOrgId in your agency |
preview | boolean | no | true returns the impact and changes nothing |
Why queued messages are cancelled
A queued message belongs to the workspace that queued it — its copy, its lead, its campaign, its daily cap. If it survived the move, the new workspace’s line would send the old workspace’s message. Cancelling is the only correct answer; the original workspace can re-queue on a line it still owns. Messages already handed to the device (sending) are not touched — flipping
them would not un-send anything. Everything already sent, delivered, failed,
fallback, cancelled or stopped is untouched, and messages on other lines
in the same workspace are untouched. Cancelled messages are stamped with
cancelReason: "line:transferred_to_other_workspace" so the reason is visible
in the unibox.
Preview first
curl https://app.tuco.ai/api/agency/lines \
-H "Authorization: Bearer tucoagency_xxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"action": "transfer",
"lineId": "6a3d7d7ef8c81f91304ed6f1",
"targetWorkspaceId": "org_3KKJL4DnbqOA406ibeGV9nOUgnk",
"preview": true
}'
{
"preview": true,
"chargedNowCents": 0,
"monthlyDeltaCents": 0,
"channel": "phone",
"currency": "usd",
"messagesToCancel": 5
}
Then move it
{
"success": true,
"chargedNowCents": 0,
"monthlyDeltaCents": 0,
"targetAddonAdded": false,
"sourceReleased": false,
"cancelledMessages": 5
}
What it costs
A transfer never charges or credits at the moment it happens. It shifts the line’s add-on between workspaces, and the recurring amount lands on the new workspace at the next billing cycle:- Target has a free slot on its plan → no billing change at all.
- Target is full →
+1add-on on the target (targetAddonAdded: true), billed next cycle. - The line was a paid extra on the source →
−1add-on there (sourceReleased: true), stops billing next cycle.
monthlyDeltaCents: 0.
Deleting a line
action: "delete" exists but is session-only. An API key gets
403 AGENCY_DELETE_SESSION_ONLY: deleting is irreversible, so it stays with a
signed-in agency owner. Moving is safe for a key because one more call moves it back.
Error responses
| Status | Code / message | When |
|---|---|---|
400 | Valid lineId is required | Missing or malformed lineId |
400 | targetWorkspaceId is required | Missing target |
400 | Line is already in that workspace | Source and target are the same |
400 | Target workspace has no active subscription… | Subscribe the target first |
400 | Target plan … doesn't support <channel> lines | Plan cannot hold this channel |
400 | Target workspace is at its … limit | Add-on hard cap reached |
401 | UNAUTHORIZED | Missing, unknown, revoked or expired key |
403 | AGENCY_KEY_REQUIRED | A workspace key (tuco_…) was used |
403 | That line is not part of your agency | The line belongs to someone else |
403 | Target workspace is not part of your agency | The target belongs to someone else |
403 | AGENCY_DELETE_SESSION_ONLY | action: "delete" with an API key |
404 | Line not found | Unknown lineId |