package main
import (
"context"
"fmt"
"log"
"os"
"github.com/AhaSend/ahasend-go"
"github.com/AhaSend/ahasend-go/api"
"github.com/AhaSend/ahasend-go/models/requests"
"github.com/AhaSend/ahasend-go/models/responses"
"github.com/google/uuid"
)
func main() {
client := api.NewAPIClient(api.WithAPIKey(os.Getenv("AHASEND_API_KEY")))
accountID := uuid.MustParse(os.Getenv("AHASEND_ACCOUNT_ID"))
ctx := context.Background()
listID := uuid.MustParse("11111111-1111-4111-8111-111111111111")
// Name each contact by email or by ID.
contactID := uuid.MustParse("22222222-2222-4222-8222-222222222222")
// The SDK sends a fresh Idempotency-Key with this POST and reuses it on
// its own retries, so a retried request is not applied twice.
response, _, err := client.ListsAPI.BatchAddListContacts(ctx, accountID, listID, requests.BatchAddListContactsRequest{
Data: []requests.BatchAddListContactInput{
{Email: ahasend.String("one@example.com")},
{ID: &contactID},
},
})
if err != nil {
log.Fatalf("Error adding contacts to the list: %v", err)
}
// Each item succeeds or fails on its own.
fmt.Printf("added=%d skipped=%d failed=%d\n", response.Added, response.Skipped, response.Failed)
for _, result := range response.Data {
if result.Outcome != responses.BatchListContactOutcomeAdded {
fmt.Printf("item %d: %s %s\n", result.Position, result.Outcome, result.Reason)
}
}
}
import { AhaSendClient } from "@ahasend/sdk";
const client = AhaSendClient.fromEnv();
const listId = "00000000-0000-4000-8000-000000000010";
// The SDK sends a fresh Idempotency-Key automatically and reuses it on its own retries.
const result = await client.lists.contacts.batchAdd(listId, {
data: [{ email: "one@example.com" }, { id: "00000000-0000-4000-8000-000000000011" }],
});
console.log("List batch completed.", { added: result.added, failed: result.failed });
curl --request POST \
--url https://api.ahasend.com/v2/accounts/{account_id}/lists/{list_id}/contacts/batch \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"data": [
{
"email": "jsmith@example.com",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
]
}
'import requests
url = "https://api.ahasend.com/v2/accounts/{account_id}/lists/{list_id}/contacts/batch"
payload = { "data": [
{
"email": "jsmith@example.com",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
] }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.ahasend.com/v2/accounts/{account_id}/lists/{list_id}/contacts/batch",
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([
'data' => [
[
'email' => 'jsmith@example.com',
'id' => '3c90c3cc-0d44-4b50-8888-8dd25736052a'
]
]
]),
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;
}HttpResponse<String> response = Unirest.post("https://api.ahasend.com/v2/accounts/{account_id}/lists/{list_id}/contacts/batch")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"data\": [\n {\n \"email\": \"jsmith@example.com\",\n \"id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.ahasend.com/v2/accounts/{account_id}/lists/{list_id}/contacts/batch")
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 \"data\": [\n {\n \"email\": \"jsmith@example.com\",\n \"id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"object": "list",
"added": 1,
"skipped": 1,
"failed": 1,
"data": [
{
"position": 1,
"outcome": "added",
"email": "<string>",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"membership": {
"object": "list_contact",
"list_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"contact_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"email": "jsmith@example.com",
"subscription_status": "unconfirmed",
"subscribed_at": "2023-11-07T05:31:56Z",
"unsubscribed_at": "2023-11-07T05:31:56Z",
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z",
"contact": {
"object": "contact",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z",
"email": "jsmith@example.com",
"first_name": "<string>",
"last_name": "<string>",
"status": "enabled",
"status_reason": "<string>",
"unsubscribed": true,
"unsubscribed_at": "2023-11-07T05:31:56Z",
"attributes": {},
"validation_status": "unvalidated",
"last_validated_at": "2023-11-07T05:31:56Z"
},
"list": {
"object": "contact_list",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z",
"name": "<string>",
"description": "<string>",
"tags": [
"<string>"
]
}
},
"reason": "<string>"
}
]
}{
"message": "Error message"
}{
"message": "Error message"
}{
"message": "Error message"
}{
"message": "Error message"
}{
"message": "A request with this idempotency key is already in progress"
}{
"message": "idempotency key was already used with a different request payload"
}{
"message": "Error message"
}{
"message": "Error message"
}Add Contacts to a List in a Batch via the API
Add up to 1,000 existing AhaSend contacts to a list in one request, by ID or email address. Existing memberships, unsubscribes included, never change.
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/AhaSend/ahasend-go"
"github.com/AhaSend/ahasend-go/api"
"github.com/AhaSend/ahasend-go/models/requests"
"github.com/AhaSend/ahasend-go/models/responses"
"github.com/google/uuid"
)
func main() {
client := api.NewAPIClient(api.WithAPIKey(os.Getenv("AHASEND_API_KEY")))
accountID := uuid.MustParse(os.Getenv("AHASEND_ACCOUNT_ID"))
ctx := context.Background()
listID := uuid.MustParse("11111111-1111-4111-8111-111111111111")
// Name each contact by email or by ID.
contactID := uuid.MustParse("22222222-2222-4222-8222-222222222222")
// The SDK sends a fresh Idempotency-Key with this POST and reuses it on
// its own retries, so a retried request is not applied twice.
response, _, err := client.ListsAPI.BatchAddListContacts(ctx, accountID, listID, requests.BatchAddListContactsRequest{
Data: []requests.BatchAddListContactInput{
{Email: ahasend.String("one@example.com")},
{ID: &contactID},
},
})
if err != nil {
log.Fatalf("Error adding contacts to the list: %v", err)
}
// Each item succeeds or fails on its own.
fmt.Printf("added=%d skipped=%d failed=%d\n", response.Added, response.Skipped, response.Failed)
for _, result := range response.Data {
if result.Outcome != responses.BatchListContactOutcomeAdded {
fmt.Printf("item %d: %s %s\n", result.Position, result.Outcome, result.Reason)
}
}
}
import { AhaSendClient } from "@ahasend/sdk";
const client = AhaSendClient.fromEnv();
const listId = "00000000-0000-4000-8000-000000000010";
// The SDK sends a fresh Idempotency-Key automatically and reuses it on its own retries.
const result = await client.lists.contacts.batchAdd(listId, {
data: [{ email: "one@example.com" }, { id: "00000000-0000-4000-8000-000000000011" }],
});
console.log("List batch completed.", { added: result.added, failed: result.failed });
curl --request POST \
--url https://api.ahasend.com/v2/accounts/{account_id}/lists/{list_id}/contacts/batch \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"data": [
{
"email": "jsmith@example.com",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
]
}
'import requests
url = "https://api.ahasend.com/v2/accounts/{account_id}/lists/{list_id}/contacts/batch"
payload = { "data": [
{
"email": "jsmith@example.com",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
] }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.ahasend.com/v2/accounts/{account_id}/lists/{list_id}/contacts/batch",
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([
'data' => [
[
'email' => 'jsmith@example.com',
'id' => '3c90c3cc-0d44-4b50-8888-8dd25736052a'
]
]
]),
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;
}HttpResponse<String> response = Unirest.post("https://api.ahasend.com/v2/accounts/{account_id}/lists/{list_id}/contacts/batch")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"data\": [\n {\n \"email\": \"jsmith@example.com\",\n \"id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.ahasend.com/v2/accounts/{account_id}/lists/{list_id}/contacts/batch")
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 \"data\": [\n {\n \"email\": \"jsmith@example.com\",\n \"id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"object": "list",
"added": 1,
"skipped": 1,
"failed": 1,
"data": [
{
"position": 1,
"outcome": "added",
"email": "<string>",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"membership": {
"object": "list_contact",
"list_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"contact_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"email": "jsmith@example.com",
"subscription_status": "unconfirmed",
"subscribed_at": "2023-11-07T05:31:56Z",
"unsubscribed_at": "2023-11-07T05:31:56Z",
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z",
"contact": {
"object": "contact",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z",
"email": "jsmith@example.com",
"first_name": "<string>",
"last_name": "<string>",
"status": "enabled",
"status_reason": "<string>",
"unsubscribed": true,
"unsubscribed_at": "2023-11-07T05:31:56Z",
"attributes": {},
"validation_status": "unvalidated",
"last_validated_at": "2023-11-07T05:31:56Z"
},
"list": {
"object": "contact_list",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"created_at": "2023-11-07T05:31:56Z",
"updated_at": "2023-11-07T05:31:56Z",
"name": "<string>",
"description": "<string>",
"tags": [
"<string>"
]
}
},
"reason": "<string>"
}
]
}{
"message": "Error message"
}{
"message": "Error message"
}{
"message": "Error message"
}{
"message": "Error message"
}{
"message": "A request with this idempotency key is already in progress"
}{
"message": "idempotency key was already used with a different request payload"
}{
"message": "Error message"
}{
"message": "Error message"
}lists:write.
Entries:
- Each entry names exactly one of
emailandid. - This operation only adds. A new membership is
confirmed. A membership that already exists is reportedalready_memberand does not change, whatever its status. So a batch never removes an unsubscribe. - No contact is created. An entry that names no contact of the account is reported
not_found.
datagives one result for each entry, in the order of the request.positionis the index of the entry.outcomeisadded,already_member,not_foundorinvalid.addedandalready_memberinclude the membership, andnot_foundandinvalidinclude areason.added,skippedandfailedcount the results.skippedcountsalready_member, andfailedcountsnot_foundandinvalid.- An entry is
invalidwhen it names neither or both ofemailandid, has any other field, has anidthat is not a UUID, or has anemailthat fails the checks of contact writes. - An entry that repeats an identifier of an earlier entry is
invalid, and so is that earlier entry. Email addresses are compared after they are trimmed and changed to lowercase, soA@example.comanda@example.comare the same. - When one contact is named once by email and once by ID, the first entry is
addedand the later one isalready_member. - One invalid entry does not stop the others. A field other than
datain the body, an emptydataarray or more than 1,000 entries is refused with400for the whole request.
429, wait before you continue. The API does not promise a fixed throughput. See API rate limits.
This operation accepts an Idempotency-Key header. The memberships and the stored response are saved together. A retry with the same key replays the 200, the 404 for a missing list or the 400 for 24 hours. See Idempotency.
Related Guides
- API authentication, Permission scopes, Idempotency.
- Batch Upsert Contacts, Lists API overview, API errors, API rate limits.
Authorizations
API key for authentication. Non-empty Security Requirement values are AhaSend API-key roles. Roles listed within one requirement object are jointly required; separate requirement objects are alternatives.
Headers
Optional idempotency key for safe request retries. Must be a unique string for each logical request.
An identical request with a completed stored outcome returns the original status and body. An in-progress
execution returns 409, a changed method/path/body returns 422, and a released 5xx execution may run again.
Keys for non-secret responses expire after 24 hours. API-key create responses include a one-time secret_key,
so successful encrypted replay responses for those operations expire after 5 minutes.
255Body
1 - 1000 elementsShow child attributes
Show child attributes
Response
Ordered per-entry outcomes
list Entries whose membership this request created
x >= 0Entries reported already_member
x >= 0Entries reported not_found or invalid
x >= 0Show child attributes
Show child attributes

