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/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()
// 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.ContactsAPI.BatchUpsertContacts(ctx, accountID, requests.BatchUpsertContactsRequest{
Data: []requests.BatchUpsertContactInput{
{Email: "one@example.com"},
{Email: "two@example.com", Unsubscribed: ahasend.Bool(true)},
},
})
if err != nil {
log.Fatalf("Error upserting contacts: %v", err)
}
// Each item succeeds or fails on its own.
fmt.Printf("created=%d updated=%d failed=%d\n", response.Created, response.Updated, response.Failed)
for _, result := range response.Data {
if result.Reason != "" {
fmt.Printf("%s: %s (%s)\n", result.Email, result.Outcome, result.Reason)
}
}
}import { AhaSendClient } from "@ahasend/sdk";
const client = AhaSendClient.fromEnv();
// The SDK sends a fresh Idempotency-Key automatically and reuses it on its own retries.
const result = await client.contacts.batchUpsert({
data: [
{ email: "new@example.com", attributes: { customer: true } },
{ email: "existing@example.com", attributes: { obsolete: null } },
],
});
console.log("Contact batch completed.", { created: result.created, updated: result.updated });
curl --request POST \
--url https://api.ahasend.com/v2/accounts/{account_id}/contacts/batch \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"data": [
{
"email": "jsmith@example.com",
"first_name": "<string>",
"last_name": "<string>",
"status_reason": "<string>",
"unsubscribed": true,
"attributes": {}
}
]
}
'import requests
url = "https://api.ahasend.com/v2/accounts/{account_id}/contacts/batch"
payload = { "data": [
{
"email": "jsmith@example.com",
"first_name": "<string>",
"last_name": "<string>",
"status_reason": "<string>",
"unsubscribed": True,
"attributes": {}
}
] }
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}/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',
'first_name' => '<string>',
'last_name' => '<string>',
'status_reason' => '<string>',
'unsubscribed' => true,
'attributes' => [
]
]
]
]),
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}/contacts/batch")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"data\": [\n {\n \"email\": \"jsmith@example.com\",\n \"first_name\": \"<string>\",\n \"last_name\": \"<string>\",\n \"status_reason\": \"<string>\",\n \"unsubscribed\": true,\n \"attributes\": {}\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.ahasend.com/v2/accounts/{account_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 \"first_name\": \"<string>\",\n \"last_name\": \"<string>\",\n \"status_reason\": \"<string>\",\n \"unsubscribed\": true,\n \"attributes\": {}\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"object": "list",
"created": 1,
"updated": 1,
"failed": 1,
"data": [
{
"position": 1,
"email": "<string>",
"outcome": "created",
"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"
},
"reason": "<string>"
}
]
}{
"message": "Error message"
}{
"message": "Error message"
}{
"message": "Error message"
}{
"message": "Error message"
}{
"message": "idempotency key was already used with a different request payload"
}{
"message": "Error message"
}{
"message": "Error message"
}Create or Update Contacts in a Batch via the API
Create or update up to 1,000 AhaSend contacts in one request, matched by email address. Each gets its own result, so one bad record does not stop the rest.
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/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()
// 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.ContactsAPI.BatchUpsertContacts(ctx, accountID, requests.BatchUpsertContactsRequest{
Data: []requests.BatchUpsertContactInput{
{Email: "one@example.com"},
{Email: "two@example.com", Unsubscribed: ahasend.Bool(true)},
},
})
if err != nil {
log.Fatalf("Error upserting contacts: %v", err)
}
// Each item succeeds or fails on its own.
fmt.Printf("created=%d updated=%d failed=%d\n", response.Created, response.Updated, response.Failed)
for _, result := range response.Data {
if result.Reason != "" {
fmt.Printf("%s: %s (%s)\n", result.Email, result.Outcome, result.Reason)
}
}
}import { AhaSendClient } from "@ahasend/sdk";
const client = AhaSendClient.fromEnv();
// The SDK sends a fresh Idempotency-Key automatically and reuses it on its own retries.
const result = await client.contacts.batchUpsert({
data: [
{ email: "new@example.com", attributes: { customer: true } },
{ email: "existing@example.com", attributes: { obsolete: null } },
],
});
console.log("Contact batch completed.", { created: result.created, updated: result.updated });
curl --request POST \
--url https://api.ahasend.com/v2/accounts/{account_id}/contacts/batch \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"data": [
{
"email": "jsmith@example.com",
"first_name": "<string>",
"last_name": "<string>",
"status_reason": "<string>",
"unsubscribed": true,
"attributes": {}
}
]
}
'import requests
url = "https://api.ahasend.com/v2/accounts/{account_id}/contacts/batch"
payload = { "data": [
{
"email": "jsmith@example.com",
"first_name": "<string>",
"last_name": "<string>",
"status_reason": "<string>",
"unsubscribed": True,
"attributes": {}
}
] }
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}/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',
'first_name' => '<string>',
'last_name' => '<string>',
'status_reason' => '<string>',
'unsubscribed' => true,
'attributes' => [
]
]
]
]),
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}/contacts/batch")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"data\": [\n {\n \"email\": \"jsmith@example.com\",\n \"first_name\": \"<string>\",\n \"last_name\": \"<string>\",\n \"status_reason\": \"<string>\",\n \"unsubscribed\": true,\n \"attributes\": {}\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.ahasend.com/v2/accounts/{account_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 \"first_name\": \"<string>\",\n \"last_name\": \"<string>\",\n \"status_reason\": \"<string>\",\n \"unsubscribed\": true,\n \"attributes\": {}\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"object": "list",
"created": 1,
"updated": 1,
"failed": 1,
"data": [
{
"position": 1,
"email": "<string>",
"outcome": "created",
"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"
},
"reason": "<string>"
}
]
}{
"message": "Error message"
}{
"message": "Error message"
}{
"message": "Error message"
}{
"message": "Error message"
}{
"message": "idempotency key was already used with a different request payload"
}{
"message": "Error message"
}{
"message": "Error message"
}contacts:write.
Matching and fields:
- Each entry needs
email. The API trims it and changes it to lowercase, then updates the contact with that address, or creates one. - On an update, a field that you leave out or send as
nulldoes not change. On a create, such a field gets the same default as in Create Contact. attributesis merged into the stored attributes, and anullvalue removes that key. See Attributes.
datagives one result for each entry, in the order of the request.positionis the index of the entry.outcomeiscreated,updatedorfailed, and a failed entry has areason.created,updatedandfailedcount the results.- An entry that fails does not stop the other entries.
- When two or more entries have the same email address after it is trimmed and changed to lowercase, all of them fail.
- An empty
dataarray, more than 1,000 entries or a malformed body 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 changes and the stored response are saved together. A retry with the same key replays the 200 or the 400 for 24 hours. See Idempotency.
Related Guides
- API authentication, Permission scopes, Idempotency.
- Contacts 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.
255Path Parameters
Body
1 - 1000 elementsShow child attributes
Show child attributes

