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.CreateContact(ctx, accountID, requests.CreateContactRequest{
Email: "person@example.com",
FirstName: ahasend.String("Pat"),
Attributes: map[string]any{"customer": true},
})
if err != nil {
log.Fatalf("Error creating contact: %v", err)
}
fmt.Printf("Created contact %s\n", response.ID)
}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 contact = await client.contacts.create({
email: "person@example.com",
first_name: "Pat",
attributes: { customer: true },
});
console.log("Contact created.", { id: contact.id, status: contact.status });
curl --request POST \
--url https://api.ahasend.com/v2/accounts/{account_id}/contacts \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"email": "person@example.com",
"first_name": "Pat",
"last_name": "Example",
"status": "enabled",
"status_reason": null,
"unsubscribed": false,
"attributes": {
"customer": true,
"score": 12.5,
"birthday": "2024-02-29",
"joined_at": "2026-09-09T10:04:56Z"
}
}
'import requests
url = "https://api.ahasend.com/v2/accounts/{account_id}/contacts"
payload = {
"email": "person@example.com",
"first_name": "Pat",
"last_name": "Example",
"status": "enabled",
"status_reason": None,
"unsubscribed": False,
"attributes": {
"customer": True,
"score": 12.5,
"birthday": "2024-02-29",
"joined_at": "2026-09-09T10:04:56Z"
}
}
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",
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([
'email' => 'person@example.com',
'first_name' => 'Pat',
'last_name' => 'Example',
'status' => 'enabled',
'status_reason' => null,
'unsubscribed' => false,
'attributes' => [
'customer' => true,
'score' => 12.5,
'birthday' => '2024-02-29',
'joined_at' => '2026-09-09T10:04:56Z'
]
]),
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")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"email\": \"person@example.com\",\n \"first_name\": \"Pat\",\n \"last_name\": \"Example\",\n \"status\": \"enabled\",\n \"status_reason\": null,\n \"unsubscribed\": false,\n \"attributes\": {\n \"customer\": true,\n \"score\": 12.5,\n \"birthday\": \"2024-02-29\",\n \"joined_at\": \"2026-09-09T10:04:56Z\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.ahasend.com/v2/accounts/{account_id}/contacts")
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 \"email\": \"person@example.com\",\n \"first_name\": \"Pat\",\n \"last_name\": \"Example\",\n \"status\": \"enabled\",\n \"status_reason\": null,\n \"unsubscribed\": false,\n \"attributes\": {\n \"customer\": true,\n \"score\": 12.5,\n \"birthday\": \"2024-02-29\",\n \"joined_at\": \"2026-09-09T10:04:56Z\"\n }\n}"
response = http.request(request)
puts response.read_body{
"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"
}{
"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 a Contact via the API
Create one contact in your AhaSend account with its name, status, unsubscribe choice and attributes. The new contact is on no list until you add it to one.
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.CreateContact(ctx, accountID, requests.CreateContactRequest{
Email: "person@example.com",
FirstName: ahasend.String("Pat"),
Attributes: map[string]any{"customer": true},
})
if err != nil {
log.Fatalf("Error creating contact: %v", err)
}
fmt.Printf("Created contact %s\n", response.ID)
}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 contact = await client.contacts.create({
email: "person@example.com",
first_name: "Pat",
attributes: { customer: true },
});
console.log("Contact created.", { id: contact.id, status: contact.status });
curl --request POST \
--url https://api.ahasend.com/v2/accounts/{account_id}/contacts \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"email": "person@example.com",
"first_name": "Pat",
"last_name": "Example",
"status": "enabled",
"status_reason": null,
"unsubscribed": false,
"attributes": {
"customer": true,
"score": 12.5,
"birthday": "2024-02-29",
"joined_at": "2026-09-09T10:04:56Z"
}
}
'import requests
url = "https://api.ahasend.com/v2/accounts/{account_id}/contacts"
payload = {
"email": "person@example.com",
"first_name": "Pat",
"last_name": "Example",
"status": "enabled",
"status_reason": None,
"unsubscribed": False,
"attributes": {
"customer": True,
"score": 12.5,
"birthday": "2024-02-29",
"joined_at": "2026-09-09T10:04:56Z"
}
}
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",
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([
'email' => 'person@example.com',
'first_name' => 'Pat',
'last_name' => 'Example',
'status' => 'enabled',
'status_reason' => null,
'unsubscribed' => false,
'attributes' => [
'customer' => true,
'score' => 12.5,
'birthday' => '2024-02-29',
'joined_at' => '2026-09-09T10:04:56Z'
]
]),
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")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"email\": \"person@example.com\",\n \"first_name\": \"Pat\",\n \"last_name\": \"Example\",\n \"status\": \"enabled\",\n \"status_reason\": null,\n \"unsubscribed\": false,\n \"attributes\": {\n \"customer\": true,\n \"score\": 12.5,\n \"birthday\": \"2024-02-29\",\n \"joined_at\": \"2026-09-09T10:04:56Z\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.ahasend.com/v2/accounts/{account_id}/contacts")
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 \"email\": \"person@example.com\",\n \"first_name\": \"Pat\",\n \"last_name\": \"Example\",\n \"status\": \"enabled\",\n \"status_reason\": null,\n \"unsubscribed\": false,\n \"attributes\": {\n \"customer\": true,\n \"score\": 12.5,\n \"birthday\": \"2024-02-29\",\n \"joined_at\": \"2026-09-09T10:04:56Z\"\n }\n}"
response = http.request(request)
puts response.read_body{
"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"
}{
"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.
Only email is required. The API trims it and changes it to lowercase. An email address that another contact of the account already has is refused with 409.
Fields:
statusisenabledwhen you leave it out or sendnull.unsubscribed: truecreates the contact as unsubscribed account-wide, and setsunsubscribed_atto the current UTC time. Campaigns do not send to an unsubscribed contact.attributesmust use the keys and types that you defined in the dashboard, and a value cannot benull. See Attributes.validation_statusandlast_validated_atare read-only.
Idempotency-Key header. The API stores a 201, a 400 and a 409 for a duplicate contact for 24 hours, and replays them for a retry with the same key. A 409 with Idempotent-Replayed: false and Retry-After means a request with the same key is still in progress. See Idempotency.
Related Guides
- API authentication, Permission scopes, Idempotency.
- Contacts API overview, Batch Upsert Contacts, 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
Account ID
Body
Creates an account-global contact without list membership. Writable attribute keys and types are derived from definitions managed in the dashboard. Validation metadata, membership, and activity are not writable.
Contact email; trimmed and lowercased before persistence
Defaults to enabled when omitted or null
enabled, disabled, blocked, null Only true creates an initial UTC unsubscription timestamp
Values are validated against dashboard-managed account attribute definitions. Attribute members cannot be null.
Show child attributes
Show child attributes
Response
Contact created successfully
Public contact projection. Attribute reads are legacy-safe: values can contain null, booleans, numbers, strings, arrays, or nested objects even though new writes accept only canonical dashboard-defined values.
Object type identifier
contact Unique identifier for the contact
When the contact was created
When the contact was last updated
Normalized contact email address
enabled, disabled, blocked Show child attributes
Show child attributes
unvalidated, valid, invalid, risky, unknown 
