Wersja: 1.0.0
Base URL:https://your-domain.com/api/v1
Uwierzytelnianie: Bearer Token (API Key)
Wszystkie żądania API wymagają klucza API przekazywanego w nagłówku Authorization:
Authorization: Bearer ns_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- Zaloguj się do panelu NetSendo
- Przejdź do Ustawienia → Klucze API
- Kliknij Utwórz nowy klucz
- Skopiuj klucz (wyświetlany tylko raz!)
| Uprawnienie | Opis |
|---|---|
subscribers:read |
Odczyt subskrybentów |
subscribers:write |
Tworzenie/edycja/usuwanie subskrybentów |
lists:read |
Odczyt list kontaktów |
tags:read |
Odczyt tagów |
webhooks:read |
Odczyt webhooków |
webhooks:write |
Tworzenie/edycja/usuwanie webhooków |
email:read |
Odczyt statusu email i mailboxów |
email:write |
Wysyłanie email |
sms:read |
Odczyt statusu SMS i providerów |
sms:write |
Wysyłanie SMS |
Uwaga: Uprawnienie
subscribers:writeautomatycznie obejmujesubscribers:read.
- Limit: 3000 żądań na minutę na klucz API
- Nagłówki odpowiedzi:
X-RateLimit-Limit,X-RateLimit-Remaining - Status przy przekroczeniu:
429 Too Many Requests
| Kod | Opis |
|---|---|
200 |
Sukces |
201 |
Zasób utworzony |
202 |
Żądanie przyjęte (async) |
400 |
Błąd walidacji |
401 |
Brak autoryzacji |
403 |
Brak uprawnień |
404 |
Nie znaleziono |
409 |
Konflikt (duplikat) |
422 |
Niewalidowane dane |
429 |
Przekroczony limit |
500 |
Błąd serwera |
Wstawki (placeholders) pozwalają na personalizację treści wiadomości Email i SMS. Używaj składni [[nazwa_pola]].
| Placeholder | Opis | Przykład |
|---|---|---|
[[email]] |
Adres email | [email protected] |
[[fname]] |
Imię | Jan |
[[!fname]] |
Imię w wołaczu (PL) | Janie |
[[lname]] |
Nazwisko | Kowalski |
[[phone]] |
Numer telefonu | +48123456789 |
[[sex]] |
Płeć | male / female |
| Placeholder | Opis |
|---|---|
[[unsubscribe]] |
Link wypisania z listy |
[[manage]] |
Link zarządzania preferencjami |
Każde zdefiniowane pole niestandardowe jest dostępne jako [[nazwa_pola]]:
[[city]] → Warszawa
[[company]] → Firma Sp. z o.o.
{
"email": "[email protected]",
"subject": "Witaj [[fname]]!",
"content": "<h1>Cześć [[fname]] [[lname]]!</h1><p>Twoja firma: [[company]]</p>",
"subscriber_id": 123
}{
"phone": "+48123456789",
"message": "Cześć [[fname]]! Mamy dla Ciebie ofertę. Szczegóły: example.com",
"subscriber_id": 123
}Uwaga: Przy batch wysyłce (do listy lub tagów) personalizacja następuje automatycznie dla każdego odbiorcy.
GET /api/v1/subscribersParametry query:
| Parametr | Typ | Opis |
|---|---|---|
contact_list_id |
integer | Filtruj po ID listy |
status |
string | active, inactive, unsubscribed, bounced |
email |
string | Szukaj po fragmencie email |
tag_id |
integer | Filtruj po tagu |
per_page |
integer | Wyników na stronę (max 100) |
sort_by |
string | Pole sortowania: created_at, email |
sort_order |
string | asc lub desc |
Przykład cURL:
curl -X GET "https://example.com/api/v1/subscribers?status=active&per_page=25" \
-H "Authorization: Bearer ns_live_xxxxxxxx"Odpowiedź:
{
"data": [
{
"id": 1,
"email": "[email protected]",
"first_name": "Jan",
"last_name": "Kowalski",
"phone": "+48123456789",
"status": "active",
"contact_list_id": 5,
"source": "api",
"tags": [
{"id": 1, "name": "VIP"}
],
"custom_fields": {
"city": "Warszawa"
},
"subscribed_at": "2025-01-15T10:30:00.000000Z",
"created_at": "2025-01-15T10:30:00.000000Z"
}
],
"links": { ... },
"meta": {
"current_page": 1,
"per_page": 25,
"total": 150
}
}GET /api/v1/subscribers/{id}Przykład:
curl -X GET "https://example.com/api/v1/subscribers/123" \
-H "Authorization: Bearer ns_live_xxxxxxxx"GET /api/v1/subscribers/by-email/{email}Przykład:
curl -X GET "https://example.com/api/v1/subscribers/by-email/[email protected]" \
-H "Authorization: Bearer ns_live_xxxxxxxx"POST /api/v1/subscribersWymagane uprawnienie: subscribers:write
Body (JSON):
{
"email": "[email protected]",
"contact_list_id": 5,
"first_name": "Maria",
"last_name": "Nowak",
"phone": "+48987654321",
"status": "active",
"source": "website",
"tags": [1, 3],
"custom_fields": {
"city": "Kraków",
"company": "Firma Sp. z o.o."
},
"ip_address": "192.168.1.1",
"user_agent": "Mozilla/5.0",
"device": "desktop"
}Parametry:
| Pole | Typ | Wymagane | Opis |
|---|---|---|---|
email |
string | ✅* | Adres email subskrybenta (wymagane dla list typu email) |
contact_list_id |
integer | ✅ | ID listy kontaktów, do której dodać subskrybenta |
first_name |
string | ❌ | Imię subskrybenta (max 255 znaków) |
last_name |
string | ❌ | Nazwisko subskrybenta (max 255 znaków) |
phone |
string | ✅* | Numer telefonu z kodem kraju (wymagane dla list typu sms, max 50 znaków) |
status |
string | ❌ | Status: active, inactive, unsubscribed, bounced (domyślnie: active) |
source |
string | ❌ | Źródło zapisu (domyślnie: api, max 255 znaków) |
tags |
array | ❌ | Tablica ID tagów do przypisania, np. [1, 3] |
custom_fields |
object | ❌ | Obiekt z polami niestandardowymi, np. {"city": "Kraków"} |
ip_address |
string | ❌ | Adres IP subskrybenta (przydatne przy proxy, np. n8n) |
user_agent |
string | ❌ | User agent przeglądarki (max 500 znaków) |
device |
string | ❌ | Typ urządzenia, np. desktop, mobile (max 50 znaków) |
* email jest wymagane dla list typu email, phone jest wymagane dla list typu sms.
Uwaga: Jeśli subskrybent o podanym adresie email już istnieje, zostanie dodany do nowej listy (lub reaktywowany) zamiast tworzenia duplikatu. W takim przypadku zwracany jest status
200zamiast201.
Odpowiedź (201):
{
"data": {
"id": 456,
"email": "[email protected]",
"first_name": "Maria",
"last_name": "Nowak",
"phone": "+48987654321",
"status": "active",
"contact_list_id": 5,
"source": "api",
"tags": [
{"id": 1, "name": "VIP"},
{"id": 3, "name": "Newsletter"}
],
"custom_fields": {
"city": "Kraków",
"company": "Firma Sp. z o.o."
},
"subscribed_at": "2025-01-15T10:30:00.000000Z",
"created_at": "2025-01-15T10:30:00.000000Z"
}
}Przykład cURL:
curl -X POST "https://example.com/api/v1/subscribers" \
-H "Authorization: Bearer ns_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"contact_list_id": 5,
"first_name": "Maria",
"last_name": "Nowak",
"tags": [1, 3],
"custom_fields": {"city": "Kraków"}
}'POST /api/v1/subscribers/batchWymagane uprawnienie: subscribers:write
Body (JSON):
{
"subscribers": [
{
"email": "[email protected]",
"contact_list_id": 5,
"first_name": "Jan",
"last_name": "Kowalski",
"tags": [1, 2],
"custom_fields": {
"city": "Warszawa"
}
},
{
"email": "[email protected]",
"contact_list_id": 5,
"first_name": "Anna"
}
]
}| Pole | Typ | Wymagane | Opis |
|---|---|---|---|
subscribers |
array | ✅ | Tablica subskrybentów (max 1000) |
Każdy element tablicy przyjmuje te same pola co POST /api/v1/subscribers.
Odpowiedź (200):
{
"data": {
"created": 45,
"updated": 53,
"skipped": 0,
"errors": [
{
"index": 2,
"email": "invalid@",
"error": "The email must be a valid email address."
}
]
},
"message": "Batch completed: 45 created, 53 updated, 0 skipped, 2 errors"
}Przykład cURL:
curl -X POST "https://example.com/api/v1/subscribers/batch" \
-H "Authorization: Bearer ns_live_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"subscribers":[{"email":"[email protected]","contact_list_id":5},{"email":"[email protected]","contact_list_id":5}]}'Uwaga: Webhooks (
subscriber.created,subscriber.subscribed) są wysyłane asynchronicznie dla każdego subskrybenta, co nie blokuje requestu.
PUT /api/v1/subscribers/{id}Wymagane uprawnienie: subscribers:write
Body (JSON):
{
"first_name": "Maria Anna",
"status": "inactive",
"custom_fields": {
"city": "Gdańsk"
}
}DELETE /api/v1/subscribers/{id}Wymagane uprawnienie: subscribers:write
Uwaga: Wykonuje soft delete (dane zachowane, ale oznaczone jako usunięte).
POST /api/v1/subscribers/{id}/tagsWymagane uprawnienie: subscribers:write
Body:
{
"tags": [1, 4, 7]
}Zastępuje wszystkie tagi subskrybenta podanymi wartościami.
GET /api/v1/listsParametry query:
| Parametr | Typ | Opis |
|---|---|---|
type |
string | email lub sms |
group_id |
integer | Filtruj po grupie |
search |
string | Szukaj po nazwie |
GET /api/v1/lists/{id}GET /api/v1/lists/{id}/subscribersParametry query:
| Parametr | Typ | Opis |
|---|---|---|
status |
string | Filtruj po statusie |
per_page |
integer | Wyników na stronę |
GET /api/v1/tagsParametry query:
| Parametr | Typ | Opis |
|---|---|---|
search |
string | Szukaj po nazwie |
per_page |
integer | Wyników na stronę |
GET /api/v1/tags/{id}POST /api/v1/lists/{id}/exportOdpowiedź (202):
{
"message": "Export started. You will receive a notification when it is ready.",
"list_id": 5
}Eksport wykonywany jest asynchronicznie. Powiadomienie zostanie wysłane po zakończeniu.
GET /api/v1/custom-fieldsParametry query:
| Parametr | Typ | Opis |
|---|---|---|
scope |
string | global lub list |
list_id |
integer | Pobierz pola globalne + dla tej listy |
public_only |
boolean | Tylko pola publiczne (widoczne w formach) |
search |
string | Szukaj po nazwie lub etykiecie |
Odpowiedź:
{
"data": [
{
"id": 1,
"name": "city",
"label": "Miasto",
"description": "Miasto zamieszkania",
"type": "text",
"placeholder": "[[city]]",
"options": null,
"default_value": null,
"is_public": true,
"is_required": false,
"scope": "global",
"contact_list_id": null
}
]
}GET /api/v1/custom-fields/{id}GET /api/v1/custom-fields/placeholdersZwraca wszystkie dostępne placeholdery systemowe i niestandardowe.
Odpowiedź:
{
"data": {
"system": [
{
"name": "email",
"placeholder": "[[email]]",
"label": "Email",
"type": "system"
},
{
"name": "fname",
"placeholder": "[[fname]]",
"label": "First Name",
"type": "system"
},
{
"name": "!fname",
"placeholder": "[[!fname]]",
"label": "First Name (Vocative)",
"type": "system"
},
{
"name": "lname",
"placeholder": "[[lname]]",
"label": "Last Name",
"type": "system"
},
{
"name": "phone",
"placeholder": "[[phone]]",
"label": "Phone",
"type": "system"
},
{
"name": "sex",
"placeholder": "[[sex]]",
"label": "Gender",
"type": "system"
},
{
"name": "unsubscribe",
"placeholder": "[[unsubscribe]]",
"label": "Unsubscribe Link",
"type": "link"
},
{
"name": "manage",
"placeholder": "[[manage]]",
"label": "Manage Preferences Link",
"type": "link"
}
],
"custom": [
{
"name": "city",
"placeholder": "[[city]]",
"label": "Miasto",
"type": "custom",
"field_type": "text"
},
{
"name": "company",
"placeholder": "[[company]]",
"label": "Firma",
"type": "custom",
"field_type": "text"
}
]
}
}POST /api/v1/email/sendWymagane uprawnienie: email:write
Body (JSON):
{
"email": "[email protected]",
"subject": "Witaj!",
"content": "<h1>Hello</h1><p>Treść wiadomości...</p>",
"preheader": "Opcjonalny preheader",
"mailbox_id": 1,
"schedule_at": "2025-12-25T10:00:00Z"
}| Pole | Typ | Wymagane | Opis |
|---|---|---|---|
email |
string | ✅ | Adres email odbiorcy |
subject |
string | ✅ | Temat wiadomości |
content |
string | ✅ | Treść HTML wiadomości |
preheader |
string | ❌ | Preheader (max 500 znaków) |
mailbox_id |
integer | ❌ | ID skrzynki nadawczej |
schedule_at |
datetime | ❌ | Zaplanuj wysyłkę |
subscriber_id |
integer | ❌ | Powiąż z subskrybentem |
Odpowiedź (202):
{
"data": {
"id": 123,
"email": "[email protected]",
"subject": "Witaj!",
"status": "queued",
"mailbox": "Główna skrzynka",
"scheduled_at": "2025-12-25T10:00:00Z"
},
"message": "Email queued successfully"
}POST /api/v1/email/batchWymagane uprawnienie: email:write
Body (JSON):
{
"subject": "Newsletter grudzień",
"content": "<h1>Nasz newsletter</h1>...",
"list_id": 5,
"schedule_at": "2025-12-25T10:00:00Z",
"excluded_list_ids": [7, 8]
}| Pole | Typ | Wymagane | Opis |
|---|---|---|---|
subject |
string | ✅ | Temat wiadomości |
content |
string | ✅ | Treść HTML |
list_id |
integer | ❌* | ID listy email |
tag_ids |
array | ❌* | Tablica ID tagów |
subscriber_ids |
array | ❌* | Tablica ID subskrybentów |
mailbox_id |
integer | ❌ | ID skrzynki nadawczej |
schedule_at |
datetime | ❌ | Zaplanuj wysyłkę |
excluded_list_ids |
array | ❌ | Listy do wykluczenia |
* Wymagane jest jedno z: list_id, tag_ids lub subscriber_ids
Odpowiedź (202):
{
"data": {
"id": 124,
"queued_count": 150,
"subject": "Newsletter grudzień",
"status": "queued",
"mailbox": "Główna skrzynka",
"scheduled_at": "2025-12-25T10:00:00Z"
},
"message": "Batch email queued for 150 recipients"
}GET /api/v1/email/status/{id}Wymagane uprawnienie: email:read
Odpowiedź:
{
"data": {
"id": 123,
"subject": "Witaj!",
"status": "scheduled",
"scheduled_at": "2025-12-25T10:00:00.000000Z",
"created_at": "2025-12-24T21:00:00.000000Z",
"stats": {
"planned": 10,
"queued": 5,
"sent": 140,
"failed": 0
}
}
}GET /api/v1/email/mailboxesWymagane uprawnienie: email:read
Odpowiedź:
{
"data": [
{
"id": 1,
"name": "Główna skrzynka",
"provider": "smtp",
"from_email": "[email protected]",
"from_name": "NetSendo",
"is_default": true
}
]
}POST /api/v1/sms/sendWymagane uprawnienie: sms:write
Body (JSON):
{
"phone": "+48123456789",
"message": "Witaj! To jest wiadomość z NetSendo.",
"provider_id": 1,
"schedule_at": "2025-12-25T10:00:00Z"
}| Pole | Typ | Wymagane | Opis |
|---|---|---|---|
phone |
string | ✅ | Numer telefonu z kodem kraju |
message |
string | ✅ | Treść SMS (max 1600 znaków) |
provider_id |
integer | ❌ | ID providera SMS |
subscriber_id |
integer | ❌ | Powiąż z subskrybentem |
schedule_at |
datetime | ❌ | Zaplanuj wysyłkę |
Odpowiedź (202):
{
"data": {
"id": 123,
"phone": "+48123456789",
"status": "queued",
"provider": "SMS API (Polska)",
"scheduled_at": null
},
"message": "SMS queued successfully"
}POST /api/v1/sms/batchWymagane uprawnienie: sms:write
Body (JSON):
{
"message": "Hej! Mamy dla Ciebie promocję.",
"list_id": 5
}lub:
{
"message": "Twoja oferta wygasa!",
"tag_ids": [1, 3]
}| Pole | Typ | Wymagane | Opis |
|---|---|---|---|
message |
string | ✅ | Treść SMS |
list_id |
integer | ❌* | ID listy SMS |
tag_ids |
array | ❌* | Tablica ID tagów |
subscriber_ids |
array | ❌* | Tablica ID subskrybentów |
provider_id |
integer | ❌ | ID providera SMS |
* Wymagane jest jedno z: list_id, tag_ids lub subscriber_ids
Odpowiedź (202):
{
"data": {
"id": 124,
"queued_count": 150,
"status": "queued",
"provider": "Twilio"
},
"message": "Batch SMS queued for 150 recipients"
}GET /api/v1/sms/status/{id}Wymagane uprawnienie: sms:read
Odpowiedź:
{
"data": {
"id": 123,
"status": "queued",
"content": "Witaj! To jest wiadomość...",
"created_at": "2025-12-24T21:00:00.000000Z",
"stats": {
"pending": 10,
"sent": 140,
"failed": 0
}
}
}GET /api/v1/sms/providersWymagane uprawnienie: sms:read
Odpowiedź:
{
"data": [
{
"id": 1,
"name": "Główny SMS",
"provider": "smsapi",
"is_default": true,
"from_name": "NetSendo",
"daily_limit": 1000,
"sent_today": 150
}
]
}const API_KEY = "ns_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const BASE_URL = "https://example.com/api/v1";
async function getSubscribers() {
const response = await fetch(`${BASE_URL}/subscribers?status=active`, {
method: "GET",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
});
return response.json();
}
async function createSubscriber(email, listId) {
const response = await fetch(`${BASE_URL}/subscribers`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
email: email,
contact_list_id: listId,
}),
});
if (response.status === 409) {
console.log("Subskrybent już istnieje na tej liście");
}
return response.json();
}<?php
$apiKey = 'ns_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
$baseUrl = 'https://example.com/api/v1';
function callApi($method, $endpoint, $data = null) {
global $apiKey, $baseUrl;
$ch = curl_init($baseUrl . $endpoint);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json'
]);
if ($method === 'POST') {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return [
'status' => $httpCode,
'data' => json_decode($response, true)
];
}
// Pobierz subskrybentów
$subscribers = callApi('GET', '/subscribers?status=active');
// Utwórz subskrybenta
$newSub = callApi('POST', '/subscribers', [
'email' => '[email protected]',
'contact_list_id' => 5
]);Po uruchomieniu aplikacji, dokumentacja OpenAPI jest dostępna pod adresem:
https://your-domain.com/docs/api
Zawiera pełną specyfikację OpenAPI z możliwością testowania endpointów bezpośrednio w przeglądarce.
Jeśli korzystałeś ze starego API NetSendo, oto mapa migracji:
| Stary endpoint | Nowy endpoint |
|---|---|
POST /api.php?action=subscribe |
POST /api/v1/subscribers |
POST /api.php?action=lists |
GET /api/v1/lists |
| Domain-based auth | Bearer Token auth |
W razie pytań lub problemów kontaktuj się z zespołem wsparcia lub otwórz issue w repozytorium projektowym.