curl --request POST \
--url https://api.listo.delivery/v1/stores/{store_id}/owner \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "Rosa Delgado",
"email": "rosa@tacoslamera.mx"
}
'import requests
url = "https://api.listo.delivery/v1/stores/{store_id}/owner"
payload = {
"name": "Rosa Delgado",
"email": "rosa@tacoslamera.mx"
}
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({name: 'Rosa Delgado', email: 'rosa@tacoslamera.mx'})
};
fetch('https://api.listo.delivery/v1/stores/{store_id}/owner', 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.listo.delivery/v1/stores/{store_id}/owner",
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([
'name' => 'Rosa Delgado',
'email' => 'rosa@tacoslamera.mx'
]),
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://api.listo.delivery/v1/stores/{store_id}/owner"
payload := strings.NewReader("{\n \"name\": \"Rosa Delgado\",\n \"email\": \"rosa@tacoslamera.mx\"\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://api.listo.delivery/v1/stores/{store_id}/owner")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"Rosa Delgado\",\n \"email\": \"rosa@tacoslamera.mx\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.listo.delivery/v1/stores/{store_id}/owner")
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 \"name\": \"Rosa Delgado\",\n \"email\": \"rosa@tacoslamera.mx\"\n}"
response = http.request(request)
puts response.read_body{
"email": "rosa@tacoslamera.mx",
"invited_at": "2026-08-01T22:17:10Z"
}{
"error": {
"code": "invalid_api_key",
"message": "The API key is missing, revoked, or not valid for this mode.",
"param": null,
"retryable": false,
"doc_url": "https://docs.listo.delivery/en/errors/invalid_api_key",
"request_id": "0b1f6c9a-7e2d-4c53-9a41-5d8e3f2a6b10"
}
}{
"error": {
"code": "read_only_key",
"message": "This API key is read-only. Use a key without the read-only setting to create, change or cancel anything.",
"param": null,
"retryable": false,
"doc_url": "https://docs.listo.delivery/en/errors/read_only_key",
"request_id": "0b1f6c9a-7e2d-4c53-9a41-5d8e3f2a6b10"
}
}{
"error": {
"code": "not_found",
"message": "No resource with that id exists for this API key.",
"param": null,
"retryable": false,
"doc_url": "https://docs.listo.delivery/en/errors/not_found",
"request_id": "0b1f6c9a-7e2d-4c53-9a41-5d8e3f2a6b10"
}
}{
"error": {
"code": "validation_failed",
"message": "This store already has an owner (rosita@tacoslamera.mx). A store has one console login, and this endpoint cannot change who holds it.",
"param": "email",
"retryable": false,
"doc_url": "https://docs.listo.delivery/en/errors/validation_failed",
"request_id": "0b1f6c9a-7e2d-4c53-9a41-5d8e3f2a6b10"
}
}Invite a store owner
Gives the person who runs the store a login to its Listo console. We create the login and email them a link to choose their own password. The response carries the address the link went to and when it was sent, and never a password or a token, so there is nothing in it to store.
Repeating the call is safe. The same address gets a fresh link rather than a second login, so a retry after a timeout does no harm, and it is also how you send the link again when the first one has expired. The link lasts seven days.
An address that already has a Listo login somewhere else is fine. That person gets a separate login for this store, with its own password, and the account they already had is left as it was. Somebody who runs two of your locations can use the same address for both.
Three refusals:
422 console_access_requiredon a store whoseaccess_tierisapi_only. That store has no console to sign in to, so there is nothing to invite anybody to. Set the tier first, then call this again.422 validation_failedwithparam: "email"when the store already has an owner at a different address. Themessagenames that owner. A store has one owner, and this endpoint cannot change who it is. To send the owner a fresh link, call it with the address themessagenames.422 validation_failedwithparam: "email"when another request is setting up the same address for this store at the same moment. Repeat the call: by then the first one has finished, and yours sends the link again.
curl --request POST \
--url https://api.listo.delivery/v1/stores/{store_id}/owner \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "Rosa Delgado",
"email": "rosa@tacoslamera.mx"
}
'import requests
url = "https://api.listo.delivery/v1/stores/{store_id}/owner"
payload = {
"name": "Rosa Delgado",
"email": "rosa@tacoslamera.mx"
}
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({name: 'Rosa Delgado', email: 'rosa@tacoslamera.mx'})
};
fetch('https://api.listo.delivery/v1/stores/{store_id}/owner', 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.listo.delivery/v1/stores/{store_id}/owner",
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([
'name' => 'Rosa Delgado',
'email' => 'rosa@tacoslamera.mx'
]),
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://api.listo.delivery/v1/stores/{store_id}/owner"
payload := strings.NewReader("{\n \"name\": \"Rosa Delgado\",\n \"email\": \"rosa@tacoslamera.mx\"\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://api.listo.delivery/v1/stores/{store_id}/owner")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"Rosa Delgado\",\n \"email\": \"rosa@tacoslamera.mx\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.listo.delivery/v1/stores/{store_id}/owner")
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 \"name\": \"Rosa Delgado\",\n \"email\": \"rosa@tacoslamera.mx\"\n}"
response = http.request(request)
puts response.read_body{
"email": "rosa@tacoslamera.mx",
"invited_at": "2026-08-01T22:17:10Z"
}{
"error": {
"code": "invalid_api_key",
"message": "The API key is missing, revoked, or not valid for this mode.",
"param": null,
"retryable": false,
"doc_url": "https://docs.listo.delivery/en/errors/invalid_api_key",
"request_id": "0b1f6c9a-7e2d-4c53-9a41-5d8e3f2a6b10"
}
}{
"error": {
"code": "read_only_key",
"message": "This API key is read-only. Use a key without the read-only setting to create, change or cancel anything.",
"param": null,
"retryable": false,
"doc_url": "https://docs.listo.delivery/en/errors/read_only_key",
"request_id": "0b1f6c9a-7e2d-4c53-9a41-5d8e3f2a6b10"
}
}{
"error": {
"code": "not_found",
"message": "No resource with that id exists for this API key.",
"param": null,
"retryable": false,
"doc_url": "https://docs.listo.delivery/en/errors/not_found",
"request_id": "0b1f6c9a-7e2d-4c53-9a41-5d8e3f2a6b10"
}
}{
"error": {
"code": "validation_failed",
"message": "This store already has an owner (rosita@tacoslamera.mx). A store has one console login, and this endpoint cannot change who holds it.",
"param": "email",
"retryable": false,
"doc_url": "https://docs.listo.delivery/en/errors/validation_failed",
"request_id": "0b1f6c9a-7e2d-4c53-9a41-5d8e3f2a6b10"
}
}Authorizations
Your API key, sent as Authorization: Bearer <key>. Live and test keys are separate; the key you send decides which world the call acts in. To try calls, use a test key (it starts with lst_test_): no real courier is sent and no real money moves, but webhooks fire and the dropoff.phone you give can receive WhatsApp messages, so use your own number. A live key (it starts with lst_live_) books and charges real deliveries.