curl --request POST \
--url https://api.tupana.ai/v2/connect-sessions/ \
--header 'Authorization: <api-key>' \
--header 'Content-Type: application/json' \
--data '
{
"capabilities": [
"invoicing"
],
"app_name": "Mi ERP",
"external_customer_id": "cust_8841"
}
'import requests
url = "https://api.tupana.ai/v2/connect-sessions/"
payload = {
"capabilities": ["invoicing"],
"app_name": "Mi ERP",
"external_customer_id": "cust_8841"
}
headers = {
"Authorization": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
capabilities: ['invoicing'],
app_name: 'Mi ERP',
external_customer_id: 'cust_8841'
})
};
fetch('https://api.tupana.ai/v2/connect-sessions/', 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://api.tupana.ai/v2/connect-sessions/",
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([
'capabilities' => [
'invoicing'
],
'app_name' => 'Mi ERP',
'external_customer_id' => 'cust_8841'
]),
CURLOPT_HTTPHEADER => [
"Authorization: <api-key>",
"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://api.tupana.ai/v2/connect-sessions/"
payload := strings.NewReader("{\n \"capabilities\": [\n \"invoicing\"\n ],\n \"app_name\": \"Mi ERP\",\n \"external_customer_id\": \"cust_8841\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "<api-key>")
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://api.tupana.ai/v2/connect-sessions/")
.header("Authorization", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"capabilities\": [\n \"invoicing\"\n ],\n \"app_name\": \"Mi ERP\",\n \"external_customer_id\": \"cust_8841\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.tupana.ai/v2/connect-sessions/")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"capabilities\": [\n \"invoicing\"\n ],\n \"app_name\": \"Mi ERP\",\n \"external_customer_id\": \"cust_8841\"\n}"
response = http.request(request)
puts response.read_body{
"connect_session_id": "cses_4829301923847120394",
"client_token": "cst_9f2b7c1e4d8a...",
"expires_at": "2026-10-29T19:45:00+00:00"
}{
"error": "capabilities debe ser una lista con al menos un valor."
}Crear sesión de Tupana Connect (API v2)
Crea la sesión de corta duración con la que tu frontend abre el widget de Tupana Connect. La llama tu backend con tu API key; al navegador solo llega el client_token.
curl --request POST \
--url https://api.tupana.ai/v2/connect-sessions/ \
--header 'Authorization: <api-key>' \
--header 'Content-Type: application/json' \
--data '
{
"capabilities": [
"invoicing"
],
"app_name": "Mi ERP",
"external_customer_id": "cust_8841"
}
'import requests
url = "https://api.tupana.ai/v2/connect-sessions/"
payload = {
"capabilities": ["invoicing"],
"app_name": "Mi ERP",
"external_customer_id": "cust_8841"
}
headers = {
"Authorization": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
capabilities: ['invoicing'],
app_name: 'Mi ERP',
external_customer_id: 'cust_8841'
})
};
fetch('https://api.tupana.ai/v2/connect-sessions/', 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://api.tupana.ai/v2/connect-sessions/",
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([
'capabilities' => [
'invoicing'
],
'app_name' => 'Mi ERP',
'external_customer_id' => 'cust_8841'
]),
CURLOPT_HTTPHEADER => [
"Authorization: <api-key>",
"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://api.tupana.ai/v2/connect-sessions/"
payload := strings.NewReader("{\n \"capabilities\": [\n \"invoicing\"\n ],\n \"app_name\": \"Mi ERP\",\n \"external_customer_id\": \"cust_8841\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "<api-key>")
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://api.tupana.ai/v2/connect-sessions/")
.header("Authorization", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"capabilities\": [\n \"invoicing\"\n ],\n \"app_name\": \"Mi ERP\",\n \"external_customer_id\": \"cust_8841\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.tupana.ai/v2/connect-sessions/")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"capabilities\": [\n \"invoicing\"\n ],\n \"app_name\": \"Mi ERP\",\n \"external_customer_id\": \"cust_8841\"\n}"
response = http.request(request)
puts response.read_body{
"connect_session_id": "cses_4829301923847120394",
"client_token": "cst_9f2b7c1e4d8a...",
"expires_at": "2026-10-29T19:45:00+00:00"
}{
"error": "capabilities debe ser una lista con al menos un valor."
}client_token. Tu API key nunca va al navegador y la clave del SII de tu
usuario nunca pasa por tu código.
Abrir el widget
Con el token abreshttps://developers.tupana.ai/connect?token=… en un iframe
o en una ventana. El token dura 30 minutos y sirve una sola vez: créalo justo
cuando tu usuario hace clic en “Conectar empresa”.
<iframe src="https://developers.tupana.ai/connect?token=cst_9f2b…"
width="460" height="660" frameborder="0"></iframe>
Confirmar la conexión
El widget te avisa porpostMessage con event: "success" cuando el usuario
termina, pero eso solo sirve para cerrar tu modal. La conexión es real cuando
tu backend recibe el webhook connection.active; si la clave estaba mala,
llega connection.requires_action. Después la ves en
Listar conexiones con su company_id.Authorizations
API Key para autenticación. Debe proporcionarse en el header Authorization con el formato: 'Api-Key YOUR-API-KEY' (incluye el prefijo 'Api-Key ' seguido de tu API key)
Body
Qué podrá hacer la conexión: invoicing emite documentos, sii_data lee el registro de compras y ventas, banking lee cuentas y movimientos. Puedes pedir varias.
invoicing, sii_data, banking RUT de la empresa a conectar. Si lo mandas, el widget no lo vuelve a pedir.
"78.152.641-K"
Nombre de tu app, se muestra dentro del widget.
120"Mi ERP"
Tu identificador del cliente. Vuelve en los webhooks de la conexión.
120"cust_8841"
Si abres el widget en una ventana en vez de un iframe, adónde vuelve al terminar.
"https://mi-erp.cl/integraciones/tupana"
Por defecto sigue al ambiente de tu API key. Una key de sandbox solo puede abrir sesiones sandbox.
sandbox, production Response
Sesión creada
Id de la sesión, con prefijo cses_.
"cses_4829301923847120394"
Token para abrir el widget: https://developers.tupana.ai/connect?token=…. Se muestra una sola vez.
"cst_9f2b7c1e4d8a..."
"2026-10-29T19:45:00+00:00"
