Direction 1: your server pushes allowed emails into ProLogbooks
Warning about monthly connector-user fees
Be careful when you add or keep allowed connector users. The system creates one monthly invoice item based on the number of allowed connector users that stayed registered for more than 2 days. It bills the active monthly connector-user unit price multiplied by that qualified user count for the billing month, so each extra allowed user can increase the monthly charge even before that person starts syncing flights.
Your server can create or refresh allowed connector users by calling the bearer-token endpoint `/api/v1/connectors/import-emails.php`. This is the actual repo endpoint meant for the partner system. It is not the same as the internal browser endpoint `/api/partner-connector-users.php`, which requires a signed-in session and CSRF token.
Use this call whenever your company onboarding process adds or removes people who should be allowed to sync through the connector. ProLogbooks deduplicates emails, skips invalid ones, and returns the stable per-user access token you must save on your side.
After the call succeeds, read the `imported_emails` array in the response and store each returned `access_token` in your partner database beside the matching user email or partner user record. Treat this value as that user’s ProLogbooks user API key. Later, when ProLogbooks checks, previews, or syncs data for that allowed user, it sends the same email and `access_token` to your partner endpoint. Your server should use that pair to recognize which partner-side user the request is about and to decide whether the sync is still allowed.
curl -X POST https://prologbooks.com/api/v1/connectors/import-emails.php \
-H "Authorization: Bearer YOUR_CONNECTOR_API_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
--data '{
"emails": [
"pilot.one@example.com",
"pilot.two@example.com",
{
"email": "pilot.three@example.com"
}
],
"remove_emails": [
"former.pilot@example.com"
]
}'
import requests
endpoint = "https://prologbooks.com/api/v1/connectors/import-emails.php"
api_key = "YOUR_CONNECTOR_API_KEY"
payload = {
"emails": [
"pilot.one@example.com",
"pilot.two@example.com",
{"email": "pilot.three@example.com"},
],
"remove_emails": [
"former.pilot@example.com",
],
}
response = requests.post(
endpoint,
headers={
"Authorization": f"Bearer {api_key}",
"Accept": "application/json",
"Content-Type": "application/json",
},
json=payload,
timeout=20,
)
print(response.status_code)
print(response.text)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class ImportConnectorEmails
{
public static void main(String[] args) throws Exception
{
String endpoint = "https://prologbooks.com/api/v1/connectors/import-emails.php";
String apiKey = "YOUR_CONNECTOR_API_KEY";
String payload = """
{
"emails": [
"pilot.one@example.com",
"pilot.two@example.com",
{ "email": "pilot.three@example.com" }
],
"remove_emails": [
"former.pilot@example.com"
]
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(endpoint))
.header("Authorization", "Bearer " + apiKey)
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpClient client = HttpClient.newHttpClient();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
}
}
const endpoint = "https://prologbooks.com/api/v1/connectors/import-emails.php";
const apiKey = "YOUR_CONNECTOR_API_KEY";
const payload = {
emails: [
"pilot.one@example.com",
"pilot.two@example.com",
{ email: "pilot.three@example.com" },
],
remove_emails: [
"former.pilot@example.com",
],
};
const response = await fetch(endpoint, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
console.log(response.status);
console.log(await response.text());
using System.Net.Http.Headers;
using System.Text;
var endpoint = "https://prologbooks.com/api/v1/connectors/import-emails.php";
var apiKey = "YOUR_CONNECTOR_API_KEY";
var payload = """
{
"emails": [
"pilot.one@example.com",
"pilot.two@example.com",
{ "email": "pilot.three@example.com" }
],
"remove_emails": [
"former.pilot@example.com"
]
}
""";
using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post, endpoint);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Content = new StringContent(payload, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
var responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine((int) response.StatusCode);
Console.WriteLine(responseBody);
<?php
declare(strict_types=1);
$endpoint = 'https://prologbooks.com/api/v1/connectors/import-emails.php';
$apiKey = 'YOUR_CONNECTOR_API_KEY';
$payload = json_encode([
'emails' => [
'pilot.one@example.com',
'pilot.two@example.com',
['email' => 'pilot.three@example.com'],
],
'remove_emails' => [
'former.pilot@example.com',
],
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$ch = curl_init($endpoint);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Accept: application/json',
'Content-Type: application/json',
'Content-Length: ' . strlen((string) $payload),
],
CURLOPT_POSTFIELDS => $payload,
CURLOPT_TIMEOUT => 20,
]);
$responseBody = curl_exec($ch);
$responseCode = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$curlError = curl_error($ch);
curl_close($ch);
if ($responseBody === false) {
throw new RuntimeException('Request failed: ' . $curlError);
}
echo "HTTP {$responseCode}\n";
echo $responseBody . "\n";
{
"emails": [
"pilot.one@example.com",
"pilot.two@example.com",
{
"email": "pilot.three@example.com"
}
],
"remove_emails": [
"former.pilot@example.com"
]
}
{
"message": "Connector email list imported.",
"connector": {
"key": "partner_connector_user_42",
"id": 7,
"name": "Partner connector",
"access_plan": 9999
},
"summary": {
"received_import": 3,
"received_remove": 1,
"accepted_import": 3,
"accepted_remove": 1,
"inserted": 2,
"updated": 1,
"deleted": 1,
"invalid": 0
},
"imported_emails": [
{
"email": "pilot.one@example.com",
"access_token": "0f7f9d926f94a91c0bf4d2a8a34ebd799753a4a4d2fd2b380c53a72b0d5db04b",
"status": "inserted"
},
{
"email": "pilot.two@example.com",
"access_token": "84a64eb5487d27af25fa917f5227d01782aaebc88ad45129d7658bd6cfaf5283",
"status": "updated"
}
],
"invalid_emails": []
}
- `emails` adds or refreshes allowed users for this connector.
- `remove_emails` removes access for this connector only.
- `imported_emails[].access_token` is the per-user API key your server must store in its database and later match against ProLogbooks requests.
- When ProLogbooks later calls your endpoints, match the incoming `email` and `access_token` to the stored partner user before returning data.
Direction 2: ProLogbooks calls your check endpoint
When a signed-in user presses Check on a connector-linked logbook, ProLogbooks sends a server-to-server `POST` to the URL saved in `endpoint_checkuser`. The repo sends the connector API key as bearer authentication and a JSON payload containing the user email plus the stable per-user access token.
Your endpoint should treat this as an allow or deny call. Return `{ "ok": true }` only when the email and access token match a real partner-side user that is still allowed to use this connector. On failure, return JSON with an `error` string that is safe for end users to see.
{
"email": "pilot@example.com",
"access_token": "stable-token-generated-by-prologbooks"
}
{
"ok": true
}
{
"error": "User not found in connector."
}
curl -X POST https://partner.example.com/connector/checkuser.php \
-H "Authorization: Bearer YOUR_CONNECTOR_API_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
--data '{
"email": "pilot@example.com",
"access_token": "stable-token-generated-by-prologbooks"
}'
import requests
endpoint = "https://partner.example.com/connector/checkuser.php"
api_key = "YOUR_CONNECTOR_API_KEY"
payload = {
"email": "pilot@example.com",
"access_token": "stable-token-generated-by-prologbooks",
}
response = requests.post(
endpoint,
headers={
"Authorization": f"Bearer {api_key}",
"Accept": "application/json",
"Content-Type": "application/json",
},
json=payload,
timeout=12,
)
print(response.status_code)
print(response.text)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class CheckConnectorUser
{
public static void main(String[] args) throws Exception
{
String endpoint = "https://partner.example.com/connector/checkuser.php";
String apiKey = "YOUR_CONNECTOR_API_KEY";
String payload = """
{
"email": "pilot@example.com",
"access_token": "stable-token-generated-by-prologbooks"
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(endpoint))
.header("Authorization", "Bearer " + apiKey)
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpClient client = HttpClient.newHttpClient();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
}
}
const endpoint = "https://partner.example.com/connector/checkuser.php";
const apiKey = "YOUR_CONNECTOR_API_KEY";
const payload = {
email: "pilot@example.com",
access_token: "stable-token-generated-by-prologbooks",
};
const response = await fetch(endpoint, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
console.log(response.status);
console.log(await response.text());
using System.Net.Http.Headers;
using System.Text;
var endpoint = "https://partner.example.com/connector/checkuser.php";
var apiKey = "YOUR_CONNECTOR_API_KEY";
var payload = """
{
"email": "pilot@example.com",
"access_token": "stable-token-generated-by-prologbooks"
}
""";
using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post, endpoint);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Content = new StringContent(payload, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
var responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine((int) response.StatusCode);
Console.WriteLine(responseBody);
<?php
declare(strict_types=1);
$endpoint = 'https://partner.example.com/connector/checkuser.php';
$apiKey = 'YOUR_CONNECTOR_API_KEY';
$payload = json_encode([
'email' => 'pilot@example.com',
'access_token' => 'stable-token-generated-by-prologbooks',
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$ch = curl_init($endpoint);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Accept: application/json',
'Content-Type: application/json',
'Content-Length: ' . strlen((string) $payload),
],
CURLOPT_POSTFIELDS => $payload,
CURLOPT_TIMEOUT => 12,
]);
$responseBody = curl_exec($ch);
$responseCode = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$curlError = curl_error($ch);
curl_close($ch);
if ($responseBody === false) {
throw new RuntimeException('Request failed: ' . $curlError);
}
echo "HTTP {$responseCode}\n";
echo $responseBody . "\n";
Direction 3: ProLogbooks calls your preview or sync endpoint
When the user opens preview, asks for pending connector flights, or runs sync, ProLogbooks calls the URL saved in `endpoint_getnewids`. The repo uses the same bearer connector API key, but the payload is larger because it includes the user token, the partner item IDs already known by the logbook, and the list of writable flight columns the current repo understands.
Send only fields your server knows. Unknown extra fields are ignored, but required flight fields such as date, departure, arrival, times, and positive block minutes must be valid or the individual item will be skipped.
{
"email": "pilot@example.com",
"access_token": "stable-token-generated-by-prologbooks",
"synced_partner_item_uids": [
"flight-10001",
"flight-10002"
],
"syncable_flight_columns": [
"flight_date",
"aircraft_type",
"aircraft_registration",
"pic_name",
"copilot_name",
"departure_airport",
"departure_lat",
"departure_long",
"departure_time",
"arrival_airport",
"arrival_lat",
"arrival_long",
"arrival_time",
"route_via",
"block_time_minutes",
"day_minutes",
"pic_time_minutes",
"sic_time_minutes",
"night_minutes",
"ifr_minutes",
"ifr_actual_minutes",
"ifr_hood_minutes",
"ifr_simulated_minutes",
"simulator_minutes",
"ground_trainer_minutes",
"single_pilot_se_minutes",
"single_pilot_me_minutes",
"multi_pilot_time_minutes",
"dual_received_minutes",
"instruction_given_minutes",
"flight_rules",
"fstd_date",
"fstd_type",
"single_engine_day_dual_minutes",
"single_engine_day_pic_minutes",
"single_engine_day_copilot_minutes",
"single_engine_night_dual_minutes",
"single_engine_night_pic_minutes",
"single_engine_night_copilot_minutes",
"multi_engine_day_dual_minutes",
"multi_engine_day_pic_minutes",
"multi_engine_day_copilot_minutes",
"multi_engine_night_dual_minutes",
"multi_engine_night_pic_minutes",
"multi_engine_night_copilot_minutes",
"cross_country_day_dual_minutes",
"cross_country_day_pic_minutes",
"cross_country_day_copilot_minutes",
"cross_country_night_dual_minutes",
"cross_country_night_pic_minutes",
"cross_country_night_copilot_minutes",
"faa_cross_country_minutes",
"instrument_approaches_count",
"approach_types",
"takeoffs_day",
"landings_day",
"takeoffs_night",
"landings_night",
"floats_minutes",
"tailwheel_minutes",
"helicopter_minutes",
"turbine_minutes",
"glass_cockpit_minutes",
"remarks"
]
}
{
"items": [
{
"partner_item_uid": "flight-10003",
"flight_date": "2026-05-01",
"aircraft_type": "C172",
"aircraft_registration": "C-GABC",
"pic_name": "Jane Pilot",
"departure_airport": "CYTZ",
"departure_lat": 43.628611,
"departure_long": -79.396667,
"departure_time": "09:10",
"arrival_airport": "CYOO",
"arrival_lat": 43.922778,
"arrival_long": -78.895,
"arrival_time": "10:35",
"block_time_minutes": 85,
"pic_time_minutes": 85,
"single_pilot_se_minutes": 85,
"takeoffs_day": 1,
"landings_day": 1,
"remarks": "Training flight"
}
]
}
curl -X POST https://partner.example.com/connector/getnewids.php \
-H "Authorization: Bearer YOUR_CONNECTOR_API_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
--data '{
"email": "pilot@example.com",
"access_token": "stable-token-generated-by-prologbooks",
"synced_partner_item_uids": [
"flight-10001",
"flight-10002"
],
"syncable_flight_columns": [
"flight_date",
"aircraft_type",
"aircraft_registration",
"departure_airport",
"departure_time",
"arrival_airport",
"arrival_time",
"block_time_minutes",
"remarks"
]
}'
import requests
endpoint = "https://partner.example.com/connector/getnewids.php"
api_key = "YOUR_CONNECTOR_API_KEY"
payload = {
"email": "pilot@example.com",
"access_token": "stable-token-generated-by-prologbooks",
"synced_partner_item_uids": [
"flight-10001",
"flight-10002",
],
"syncable_flight_columns": [
"flight_date",
"aircraft_type",
"aircraft_registration",
"departure_airport",
"departure_time",
"arrival_airport",
"arrival_time",
"block_time_minutes",
"remarks",
],
}
response = requests.post(
endpoint,
headers={
"Authorization": f"Bearer {api_key}",
"Accept": "application/json",
"Content-Type": "application/json",
},
json=payload,
timeout=20,
)
print(response.status_code)
print(response.text)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class GetNewConnectorFlights
{
public static void main(String[] args) throws Exception
{
String endpoint = "https://partner.example.com/connector/getnewids.php";
String apiKey = "YOUR_CONNECTOR_API_KEY";
String payload = """
{
"email": "pilot@example.com",
"access_token": "stable-token-generated-by-prologbooks",
"synced_partner_item_uids": [
"flight-10001",
"flight-10002"
],
"syncable_flight_columns": [
"flight_date",
"aircraft_type",
"aircraft_registration",
"departure_airport",
"departure_time",
"arrival_airport",
"arrival_time",
"block_time_minutes",
"remarks"
]
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(endpoint))
.header("Authorization", "Bearer " + apiKey)
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpClient client = HttpClient.newHttpClient();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
}
}
const endpoint = "https://partner.example.com/connector/getnewids.php";
const apiKey = "YOUR_CONNECTOR_API_KEY";
const payload = {
email: "pilot@example.com",
access_token: "stable-token-generated-by-prologbooks",
synced_partner_item_uids: [
"flight-10001",
"flight-10002",
],
syncable_flight_columns: [
"flight_date",
"aircraft_type",
"aircraft_registration",
"departure_airport",
"departure_time",
"arrival_airport",
"arrival_time",
"block_time_minutes",
"remarks",
],
};
const response = await fetch(endpoint, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
console.log(response.status);
console.log(await response.text());
using System.Net.Http.Headers;
using System.Text;
var endpoint = "https://partner.example.com/connector/getnewids.php";
var apiKey = "YOUR_CONNECTOR_API_KEY";
var payload = """
{
"email": "pilot@example.com",
"access_token": "stable-token-generated-by-prologbooks",
"synced_partner_item_uids": [
"flight-10001",
"flight-10002"
],
"syncable_flight_columns": [
"flight_date",
"aircraft_type",
"aircraft_registration",
"departure_airport",
"departure_time",
"arrival_airport",
"arrival_time",
"block_time_minutes",
"remarks"
]
}
""";
using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post, endpoint);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Content = new StringContent(payload, Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
var responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine((int) response.StatusCode);
Console.WriteLine(responseBody);
<?php
declare(strict_types=1);
$endpoint = 'https://partner.example.com/connector/getnewids.php';
$apiKey = 'YOUR_CONNECTOR_API_KEY';
$payload = json_encode([
'email' => 'pilot@example.com',
'access_token' => 'stable-token-generated-by-prologbooks',
'synced_partner_item_uids' => [
'flight-10001',
'flight-10002',
],
'syncable_flight_columns' => [
'flight_date',
'aircraft_type',
'aircraft_registration',
'departure_airport',
'departure_time',
'arrival_airport',
'arrival_time',
'block_time_minutes',
'remarks',
],
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
$ch = curl_init($endpoint);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Accept: application/json',
'Content-Type: application/json',
'Content-Length: ' . strlen((string) $payload),
],
CURLOPT_POSTFIELDS => $payload,
CURLOPT_TIMEOUT => 20,
]);
$responseBody = curl_exec($ch);
$responseCode = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$curlError = curl_error($ch);
curl_close($ch);
if ($responseBody === false) {
throw new RuntimeException('Request failed: ' . $curlError);
}
echo "HTTP {$responseCode}\n";
echo $responseBody . "\n";
Important response shape
-
Important
Return JSON only
The response must be JSON only. ProLogbooks accepts either an object with an `items` array or a top-level array. Every returned flight item needs one stable unique identifier under `partner_item_uid`, `partner_item_id`, or `unique_id`.
If something goes wrong
- If ProLogbooks says the response is invalid, your endpoint probably returned HTML, notices, blank output, or malformed JSON.
- If a flight is missing after sync, confirm it had a stable unique ID and all required flight fields.
- If preview stages rows but import blocks them, the user still needs to resolve unknown registrations or airports in the review UI.