Create Line Request
curl --request POST \
--url https://app.tuco.ai/api/line-requests \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"firstName": "<string>",
"lastName": "<string>",
"channelType": "<string>",
"lineType": "<string>",
"email": "<string>",
"phone": "<string>",
"zipcode": "<string>",
"state": "<string>",
"forwardCallsTo": "<string>",
"profileImageUrl": "<string>",
"idempotencyKey": "<string>"
}
'import requests
url = "https://app.tuco.ai/api/line-requests"
payload = {
"firstName": "<string>",
"lastName": "<string>",
"channelType": "<string>",
"lineType": "<string>",
"email": "<string>",
"phone": "<string>",
"zipcode": "<string>",
"state": "<string>",
"forwardCallsTo": "<string>",
"profileImageUrl": "<string>",
"idempotencyKey": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
firstName: '<string>',
lastName: '<string>',
channelType: '<string>',
lineType: '<string>',
email: '<string>',
phone: '<string>',
zipcode: '<string>',
state: '<string>',
forwardCallsTo: '<string>',
profileImageUrl: '<string>',
idempotencyKey: '<string>'
})
};
fetch('https://app.tuco.ai/api/line-requests', 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/line-requests",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'firstName' => '<string>',
'lastName' => '<string>',
'channelType' => '<string>',
'lineType' => '<string>',
'email' => '<string>',
'phone' => '<string>',
'zipcode' => '<string>',
'state' => '<string>',
'forwardCallsTo' => '<string>',
'profileImageUrl' => '<string>',
'idempotencyKey' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://app.tuco.ai/api/line-requests"
payload := strings.NewReader("{\n \"firstName\": \"<string>\",\n \"lastName\": \"<string>\",\n \"channelType\": \"<string>\",\n \"lineType\": \"<string>\",\n \"email\": \"<string>\",\n \"phone\": \"<string>\",\n \"zipcode\": \"<string>\",\n \"state\": \"<string>\",\n \"forwardCallsTo\": \"<string>\",\n \"profileImageUrl\": \"<string>\",\n \"idempotencyKey\": \"<string>\"\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))
}HttpResponse<String> response = Unirest.post("https://app.tuco.ai/api/line-requests")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"firstName\": \"<string>\",\n \"lastName\": \"<string>\",\n \"channelType\": \"<string>\",\n \"lineType\": \"<string>\",\n \"email\": \"<string>\",\n \"phone\": \"<string>\",\n \"zipcode\": \"<string>\",\n \"state\": \"<string>\",\n \"forwardCallsTo\": \"<string>\",\n \"profileImageUrl\": \"<string>\",\n \"idempotencyKey\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.tuco.ai/api/line-requests")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"firstName\": \"<string>\",\n \"lastName\": \"<string>\",\n \"channelType\": \"<string>\",\n \"lineType\": \"<string>\",\n \"email\": \"<string>\",\n \"phone\": \"<string>\",\n \"zipcode\": \"<string>\",\n \"state\": \"<string>\",\n \"forwardCallsTo\": \"<string>\",\n \"profileImageUrl\": \"<string>\",\n \"idempotencyKey\": \"<string>\"\n}"
response = http.request(request)
puts response.read_bodyLines & Leads
Create Line Request
Create a new phone or email line request for the authenticated workspace. REST endpoint in the Tuco AI iMessage API — bearer-token auth, JSON request/response.
POST
/
api
/
line-requests
Create Line Request
curl --request POST \
--url https://app.tuco.ai/api/line-requests \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"firstName": "<string>",
"lastName": "<string>",
"channelType": "<string>",
"lineType": "<string>",
"email": "<string>",
"phone": "<string>",
"zipcode": "<string>",
"state": "<string>",
"forwardCallsTo": "<string>",
"profileImageUrl": "<string>",
"idempotencyKey": "<string>"
}
'import requests
url = "https://app.tuco.ai/api/line-requests"
payload = {
"firstName": "<string>",
"lastName": "<string>",
"channelType": "<string>",
"lineType": "<string>",
"email": "<string>",
"phone": "<string>",
"zipcode": "<string>",
"state": "<string>",
"forwardCallsTo": "<string>",
"profileImageUrl": "<string>",
"idempotencyKey": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
firstName: '<string>',
lastName: '<string>',
channelType: '<string>',
lineType: '<string>',
email: '<string>',
phone: '<string>',
zipcode: '<string>',
state: '<string>',
forwardCallsTo: '<string>',
profileImageUrl: '<string>',
idempotencyKey: '<string>'
})
};
fetch('https://app.tuco.ai/api/line-requests', 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/line-requests",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'firstName' => '<string>',
'lastName' => '<string>',
'channelType' => '<string>',
'lineType' => '<string>',
'email' => '<string>',
'phone' => '<string>',
'zipcode' => '<string>',
'state' => '<string>',
'forwardCallsTo' => '<string>',
'profileImageUrl' => '<string>',
'idempotencyKey' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://app.tuco.ai/api/line-requests"
payload := strings.NewReader("{\n \"firstName\": \"<string>\",\n \"lastName\": \"<string>\",\n \"channelType\": \"<string>\",\n \"lineType\": \"<string>\",\n \"email\": \"<string>\",\n \"phone\": \"<string>\",\n \"zipcode\": \"<string>\",\n \"state\": \"<string>\",\n \"forwardCallsTo\": \"<string>\",\n \"profileImageUrl\": \"<string>\",\n \"idempotencyKey\": \"<string>\"\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))
}HttpResponse<String> response = Unirest.post("https://app.tuco.ai/api/line-requests")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"firstName\": \"<string>\",\n \"lastName\": \"<string>\",\n \"channelType\": \"<string>\",\n \"lineType\": \"<string>\",\n \"email\": \"<string>\",\n \"phone\": \"<string>\",\n \"zipcode\": \"<string>\",\n \"state\": \"<string>\",\n \"forwardCallsTo\": \"<string>\",\n \"profileImageUrl\": \"<string>\",\n \"idempotencyKey\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.tuco.ai/api/line-requests")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"firstName\": \"<string>\",\n \"lastName\": \"<string>\",\n \"channelType\": \"<string>\",\n \"lineType\": \"<string>\",\n \"email\": \"<string>\",\n \"phone\": \"<string>\",\n \"zipcode\": \"<string>\",\n \"state\": \"<string>\",\n \"forwardCallsTo\": \"<string>\",\n \"profileImageUrl\": \"<string>\",\n \"idempotencyKey\": \"<string>\"\n}"
response = http.request(request)
puts response.read_bodyUse this endpoint after the workspace already has an active Tuco subscription. If the request is included in the current plan, Tuco starts provisioning immediately. If the request requires an add-on, Tuco returns a hosted invoice link.
What This Endpoint Decides
You send the line details once, and Tuco decides what should happen next. The response will always fall into one of these buckets:provisioningTuco accepted the request immediately and has started setting up the line.awaiting_paymentTuco saved the request, but it needs payment before provisioning can begin.subscription_requiredThe workspace needs an active base plan before it can request lines.forbidden_line_typeThe current plan does not allow that request, or the plan reached a hard cap with no paid expansion path.
Authentication
Pass your workspace API key as a Bearer token, or use a Clerk session token.Authorization: Bearer tuco_xxxxxxxxxxxxx
Request body
string
Required. Display first name for the line.
string
Required. Display last name for the line.
string
Required line channel. Supported values:
"phone" or "email".string
Optional for phone lines. Supported values:
"purchased" or "byon". Defaults to "purchased" when omitted.string
Required for email lines. Also required for BYON phone lines.
string
Required for BYON phone lines.
string
Optional. Used for purchased phone provisioning when relevant.
string
Optional. Used for purchased phone provisioning when relevant.
string
Optional. Forwarding destination for phone lines.
string
Optional. Avatar shown in Tuco.
string
Optional. You can also send this as the
Idempotency-Key header. Reusing the same key returns the original response instead of creating a duplicate request.Required field rules
| Request type | Required fields |
|---|---|
| Email line | firstName, lastName, channelType=email, email |
| Purchased phone line | firstName, lastName, channelType=phone |
| BYON phone line | firstName, lastName, channelType=phone, lineType=byon, email, phone |
Default behavior for phone lines
IfchannelType is phone and lineType is omitted, Tuco treats the request as:
{ "lineType": "purchased" }
lineType: "byon".
Example: included line
curl -X POST "https://app.tuco.ai/api/line-requests" \
-H "Authorization: Bearer tuco_xxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: line-req-001" \
-d '{
"firstName": "Sales",
"lastName": "Line 1",
"channelType": "phone",
"lineType": "purchased"
}'
Success: provisioning (201 Created)
{
"success": true,
"status": "provisioning",
"paymentRequired": false,
"line": {
"_id": "6a091e74542d9e3d645925ae",
"firstName": "Sales",
"lastName": "Line 1",
"channelType": "phone",
"lineType": "purchased",
"provisioningStatus": "provisioning",
"paymentStatus": "not_required"
}
}
Example: paid add-on line
curl -X POST "https://app.tuco.ai/api/line-requests" \
-H "Authorization: Bearer tuco_xxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: line-req-002" \
-d '{
"firstName": "Support",
"lastName": "Email 2",
"channelType": "email",
"email": "support2@example.com"
}'
Success: awaiting payment (201 Created)
{
"success": true,
"status": "awaiting_payment",
"paymentRequired": true,
"paymentReason": "over_plan_limit",
"paymentUrl": "https://invoice.stripe.com/...",
"expiresAt": null,
"line": {
"_id": "6a09279daa903300e2530b0f",
"firstName": "Support",
"lastName": "Email 2",
"channelType": "email",
"lineType": "purchased",
"provisioningStatus": "awaiting_payment",
"paymentStatus": "awaiting_payment",
"stripeInvoiceId": "in_123",
"paymentLinkUrl": "https://invoice.stripe.com/..."
}
}
What To Do After You Create A Request
If the response isprovisioning:
- keep the returned
line._id - poll
GET /api/line-requests/:idfor status updates
awaiting_payment:
- keep the returned
line._id - show the
paymentUrlto the user - after payment, poll
GET /api/line-requests/:iduntil it changes toprovisioning
One Request At A Time
Tuco blocks a new request only when the workspace already has a line request inawaiting_payment.
This prevents:
- duplicate unpaid requests
- accidental double purchases
- race conditions from retries
provisioning do not block the next request.
Error responses
| Status | When | Body |
|---|---|---|
400 | Missing required fields or invalid types | { "error": "..." } |
401 | Missing or invalid API key / session | { "error": "Unauthorized" } |
403 | Workspace has no active subscription | { "success": false, "status": "subscription_required", "message": "...", "redirect": "/billing" } |
403 | Line type is not allowed on the plan, or hard cap reached with no paid expansion | { "success": false, "status": "forbidden_line_type", "message": "..." } |
409 | Another line request is already waiting for payment | { "success": false, "status": "request_in_progress", "message": "...", "existingLine": { ... } } |
429 | Rate limit exceeded | { "error": "Rate limit exceeded", "retryAfterMs": 1234 } |
Notes
- Payment is always attached to the exact saved line request. The caller does not need to resubmit the payload after paying.
- The API uses the authenticated workspace. You cannot request lines for an arbitrary workspace id in the body.
- Retry safety is built in through idempotency support and workspace-level request locking.