Flight Reader API Documentation
Authentication
| TYPE | VALUE | WHEN TO USE |
|---|---|---|
| Secret | sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | Use this key on the server side. It must be kept secret and stored securely in your web or application's server-side code. Don't expose this key on a website or embed it in a client-side application. |
| Public | pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | Use this key on the client side. It can be exposed on a website or in a client-side application. A request signature must be included in all requests using a public key. |
Request Signing
When authenticating with a public API key, include the following headers. Requests authenticated with a secret API key do not require signing.
- Authorization
Use Bearer pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx with your public API key. - Api-Timestamp
The current UTC Unix timestamp in seconds. The timestamp must be within five minutes of the API server's current time. - Api-Signature
The lowercase hexadecimal HMAC-SHA256 signature calculated using the matching secret API key.
Construct the string to sign by concatenating HTTP method + request path + form data + timestamp, without additional separators:
- Use the uppercase HTTP method, such as GET or POST.
- Use the exact request path, including /v1 and actual route parameter values. Exclude the scheme, hostname, and query string.
- For multipart POST requests, join the text form fields as name=value pairs separated by &, in the order their distinct names first appear in the submitted form. Join repeated values for the same field with commas. Use the original field names and text values without URL encoding. Exclude uploaded files, file contents, and multipart boundaries. For requests without text form fields, use an empty string.
- Append the exact timestamp sent in Api-Timestamp.
Calculate HMAC-SHA256 over the UTF-8 bytes of this string, using the UTF-8 bytes of the matching secret API key as the HMAC key. Send the resulting lowercase hexadecimal digest in Api-Signature.
Generate signatures on your server so the secret API key stays private. A client using the public key sends the signature and timestamp supplied by your server. Requests with an invalid signature or timestamp return 401 Unauthorized.
Response Compression
Errors
Versioning
- Adding a new API resource.
- Adding a new optional request parameter to an existing API method.
- Adding a new property to an existing API response.
- Changing the order of properties in existing API responses.
- Changing the length or format of strings in error messages, field descriptions, or other human-readable strings.
Resources
POST /v1/keys Non-Billable
Creates a new set of secret and public API keys.
GET /v1/keys Non-Billable
Retrieves all active API keys.
DELETE /v1/keys/{key} Non-Billable
Deletes a set of secret and public API keys.
GET /v1/fields Non-Billable
Retrieves a list of all available flight log fields.
GET /v1/fields/{field-name} Non-Billable
Retrieves a single flight log field by name.
POST /v1/logs Billable
Uploads a DJI TXT flight log file and returns the processed data in CSV format.
POST /v1/logs Billable
Processes a DJI Cloud download URL or DJI SmartFarm processing reference and returns the processed data in CSV format.
POST /v1/accounts/dji/cloud Billable
Retrieves a DJI Cloud account, which is required to list, download, or process flight logs stored in the DJI Cloud.
POST /v1/accounts/dji/smartfarm Billable
Retrieves a DJI SmartFarm account, which is required to list or process flight logs stored in the DJI SmartFarm.
GET /v1/logs/{id} Billable
Retrieves a list of flight logs from DJI Cloud or DJI SmartFarm.
GET /v1/files/flight-logs/{id}/{filename} Billable
Downloads a DJI TXT flight log from the DJI Cloud as a ZIP file.
GET /v1/usage Non-Billable
Retrieves a list of billable API requests made by your API account.
GET /v1/usage/{key} Non-Billable
Retrieves billable API usage for one key belonging to your API account.
Create API key
POST /v1/keys
Creates a new set of secret and public API keys in your account.
Returns
Returns a 201 Created JSON response with the newly created publicKey and secretKey in result.
<html>
<body>
<div id="output">Creating API key...</div>
<script>
// Replace with your Flight Reader API secret key
const secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Note: You should never expose your API secret key like this in a production environment.
// Anyone who has access to it will be able to make API requests on your behalf.
async function createApiKey() {
try {
const response = await fetch("https://api.flightreader.com/v1/keys", {
method: "POST",
headers: {
"Authorization": `Bearer ${secretApiKey}`
}
});
if (response.ok) {
const data = await response.json();
output.textContent = JSON.stringify(data.result);
} else {
output.textContent = `${response.status} ${response.statusText}`;
}
} catch (error) {
output.textContent = error;
}
}
createApiKey();
</script>
</body>
</html>
using System.IO.Compression;
using System.Text;
internal class Program
{
static async Task Main()
{
// Replace with your Flight Reader API secret key
const string secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
using HttpClient client = new();
client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip");
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {secretApiKey}");
var response = await client.PostAsync("https://api.flightreader.com/v1/keys", null);
if (response.IsSuccessStatusCode)
{
var responseBytes = await response.Content.ReadAsByteArrayAsync();
if (response.Content.Headers.ContentEncoding.Contains("gzip"))
{
using var compressedStream = new MemoryStream(responseBytes);
await using var decompressionStream = new GZipStream(compressedStream, CompressionMode.Decompress);
using var decompressedStream = new MemoryStream();
await decompressionStream.CopyToAsync(decompressedStream);
responseBytes = decompressedStream.ToArray();
}
var responseText = Encoding.UTF8.GetString(responseBytes);
Console.WriteLine(responseText);
}
else
{
Console.WriteLine($"Error: {response.StatusCode}");
}
}
}
{
"statusCode": 201,
"message": "POST Request successful.",
"result": {
"publicKey": "pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"secretKey": "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
}
Retrieve API keys
GET /v1/keys
Retrieves all active API keys from your account.
Returns
Returns a JSON response with an array of active API key pairs in result. Each pair includes publicKey, secretKey, and its creation time, createdUtc.
<html>
<body>
<div id="output">Retrieving API keys...</div>
<script>
// Replace with your Flight Reader API secret key
const secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Note: You should never expose your API secret key like this in a production environment.
// Anyone who has access to it will be able to make API requests on your behalf.
async function retrieveApiKeys() {
try {
const response = await fetch("https://api.flightreader.com/v1/keys", {
headers: {
"Authorization": `Bearer ${secretApiKey}`
}
});
if (response.ok) {
const data = await response.json();
output.textContent = JSON.stringify(data.result);
} else {
output.textContent = `${response.status} ${response.statusText}`;
}
} catch (error) {
output.textContent = error;
}
}
retrieveApiKeys();
</script>
</body>
</html>
using System.IO.Compression;
using System.Text;
internal class Program
{
static async Task Main()
{
// Replace with your Flight Reader API secret key
const string secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
using HttpClient client = new();
client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip");
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {secretApiKey}");
var response = await client.GetAsync("https://api.flightreader.com/v1/keys");
if (response.IsSuccessStatusCode)
{
var responseBytes = await response.Content.ReadAsByteArrayAsync();
if (response.Content.Headers.ContentEncoding.Contains("gzip"))
{
using var compressedStream = new MemoryStream(responseBytes);
await using var decompressionStream = new GZipStream(compressedStream, CompressionMode.Decompress);
using var decompressedStream = new MemoryStream();
await decompressionStream.CopyToAsync(decompressedStream);
responseBytes = decompressedStream.ToArray();
}
var responseText = Encoding.UTF8.GetString(responseBytes);
Console.WriteLine(responseText);
}
else
{
Console.WriteLine($"Error: {response.StatusCode}");
}
}
}
{
"statusCode": 200,
"message": "GET Request successful.",
"result": [
{
"publicKey": "pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"secretKey": "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"createdUtc": "2023-08-21T11:37:38Z"
}
]
}
Delete API keys
DELETE /v1/keys/{key}
Deletes a set of secret and public API keys from your account.
Route Parameters
- key string Required
Replace {key} with the secret or public API key from the key pair you want to delete.
Returns
Returns a 200 OK JSON response confirming that the API key pair was deleted. No resource data is returned in result.
<html>
<body>
<div id="output">Deleting API key...</div>
<script>
// Replace with your Flight Reader API secret key
const secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Note: You should never expose your API secret key like this in a production environment.
// Anyone who has access to it will be able to make API requests on your behalf.
// Set this to the secret key to delete. The associated public key will also be deleted.
const secretApiKeyToDelete = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
async function deleteApiKey() {
try {
const response = await fetch(`https://api.flightreader.com/v1/keys/${secretApiKeyToDelete}`, {
method: "DELETE",
headers: {
"Authorization": `Bearer ${secretApiKey}`
}
});
if (response.ok) {
const data = await response.json();
output.textContent = JSON.stringify(data);
} else {
output.textContent = `${response.status} ${response.statusText}`;
}
} catch (error) {
output.textContent = error;
}
}
deleteApiKey();
</script>
</body>
</html>
using System.IO.Compression;
using System.Text;
internal class Program
{
static async Task Main()
{
// Replace with your Flight Reader API secret key
const string secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Set this to the secret key to delete. The associated public key will also be deleted.
const string secretApiKeyToDelete = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
using HttpClient client = new();
client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip");
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {secretApiKey}");
var response = await client.DeleteAsync($"https://api.flightreader.com/v1/keys/{secretApiKeyToDelete}");
if (response.IsSuccessStatusCode)
{
var responseBytes = await response.Content.ReadAsByteArrayAsync();
if (response.Content.Headers.ContentEncoding.Contains("gzip"))
{
using var compressedStream = new MemoryStream(responseBytes);
await using var decompressionStream = new GZipStream(compressedStream, CompressionMode.Decompress);
using var decompressedStream = new MemoryStream();
await decompressionStream.CopyToAsync(decompressedStream);
responseBytes = decompressedStream.ToArray();
}
var responseText = Encoding.UTF8.GetString(responseBytes);
Console.WriteLine(responseText);
}
else
{
Console.WriteLine($"Error: {response.StatusCode}");
}
}
}
{
"statusCode": 200,
"message": "DELETE Request successful."
}
Retrieve all flight log fields
GET /v1/fields
Retrieves a list of all available flight log fields. These field names can be used to specify which fields to return when processing a flight log.
Returns
Returns a JSON response with an array of available flight log fields in result. Each field includes its name and description.
<html>
<body>
<div id="output">Retrieving log fields...</div>
<script>
// Replace with your Flight Reader API secret key
const secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Note: You should never expose your API secret key like this in a production environment.
// Anyone who has access to it will be able to make API requests on your behalf.
async function retrieveLogFields() {
try {
const response = await fetch("https://api.flightreader.com/v1/fields", {
headers: {
"Authorization": `Bearer ${secretApiKey}`
}
});
if (response.ok) {
const data = await response.json();
output.textContent = JSON.stringify(data.result);
} else {
output.textContent = `${response.status} ${response.statusText}`;
}
} catch (error) {
output.textContent = error;
}
}
retrieveLogFields();
</script>
</body>
</html>
<html>
<body>
<!-- https://github.com/brix/crypto-js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
<div id="output">Retrieving log fields...</div>
<script>
// Replace with your Flight Reader API keys
const publicApiKey = "pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Note: You should never expose your API secret key like this in a production environment.
// Anyone who has access to it will be able to make API requests on your behalf.
async function retrieveLogFields() {
try {
// Create a request signature used by the Flight Reader API to verify the request was
// initiated by you. This signature will expire 5 minutes after it's created.
const signature = createSignature();
const response = await fetch("https://api.flightreader.com/v1/fields", {
headers: {
"Authorization": `Bearer ${publicApiKey}`,
"Api-Signature": signature.value,
"Api-Timestamp": signature.currentUnixTimeInSeconds
}
});
if (response.ok) {
const data = await response.json();
output.textContent = JSON.stringify(data.result);
} else {
output.textContent = `${response.status} ${response.statusText}`;
}
} catch (error) {
output.textContent = error;
}
}
function createSignature() {
const currentUnixTimeInSeconds = Math.floor(Date.now() / 1000);
const requestMethod = "GET";
const requestResource = "/v1/fields";
const stringToSign = requestMethod + requestResource + currentUnixTimeInSeconds;
const value = CryptoJS.HmacSHA256(stringToSign, secretApiKey).toString(CryptoJS.enc.Hex);
return { value, currentUnixTimeInSeconds };
}
retrieveLogFields();
</script>
</body>
</html>
using System.IO.Compression;
using System.Text;
internal class Program
{
static async Task Main()
{
// Replace with your Flight Reader API secret key
const string secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
using HttpClient client = new();
client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip");
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {secretApiKey}");
var response = await client.GetAsync("https://api.flightreader.com/v1/fields");
if (response.IsSuccessStatusCode)
{
var responseBytes = await response.Content.ReadAsByteArrayAsync();
if (response.Content.Headers.ContentEncoding.Contains("gzip"))
{
using var compressedStream = new MemoryStream(responseBytes);
await using var decompressionStream = new GZipStream(compressedStream, CompressionMode.Decompress);
using var decompressedStream = new MemoryStream();
await decompressionStream.CopyToAsync(decompressedStream);
responseBytes = decompressedStream.ToArray();
}
var responseText = Encoding.UTF8.GetString(responseBytes);
Console.WriteLine(responseText);
}
else
{
Console.WriteLine($"Error: {response.StatusCode}");
}
}
}
using System.IO.Compression;
using System.Security.Cryptography;
using System.Text;
internal class Program
{
static async Task Main()
{
// Replace with your Flight Reader API keys
const string publicApiKey = "pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const string secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Create a request signature used by the Flight Reader API to verify the request was
// initiated by you. This signature will expire 5 minutes after it's created.
var currentUnixTimeInSeconds = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var requestMethod = "GET";
var requestResource = "/v1/fields";
var stringToSign = requestMethod + requestResource + currentUnixTimeInSeconds;
var signature = CreateSignature(stringToSign, secretApiKey);
using HttpClient client = new();
client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip");
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {publicApiKey}");
client.DefaultRequestHeaders.Add("Api-Signature", signature);
client.DefaultRequestHeaders.Add("Api-Timestamp", currentUnixTimeInSeconds.ToString());
var response = await client.GetAsync("https://api.flightreader.com/v1/fields");
if (response.IsSuccessStatusCode)
{
var responseBytes = await response.Content.ReadAsByteArrayAsync();
if (response.Content.Headers.ContentEncoding.Contains("gzip"))
{
using var compressedStream = new MemoryStream(responseBytes);
await using var decompressionStream = new GZipStream(compressedStream, CompressionMode.Decompress);
using var decompressedStream = new MemoryStream();
await decompressionStream.CopyToAsync(decompressedStream);
responseBytes = decompressedStream.ToArray();
}
var responseText = Encoding.UTF8.GetString(responseBytes);
Console.WriteLine(responseText);
}
else
{
Console.WriteLine($"Error: {response.StatusCode}");
}
}
private static string CreateSignature(string value, string key)
{
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
var dataBytes = Encoding.UTF8.GetBytes(value);
var hashBytes = hmac.ComputeHash(dataBytes);
return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
}
}
{
"statusCode": 200,
"message": "GET Request successful.",
"result": [
{
"name": "APP.message",
"description": "An informational message generated by the application being used to control the aircraft."
},
{
"name": "APP.tip",
"description": "An informational message generated by the application being used to control the aircraft."
}
]
}
Retrieve flight log field
GET /v1/fields/{field-name}
Retrieves a single flight log field by name.
Route Parameters
- field-name string Required
Replace {field-name} with a field name returned by GET /v1/fields.
Returns
Returns a JSON response with the matching field's name and description in result. Returns 404 Not Found if the field does not exist.
<html>
<body>
<div id="output">Retrieving log field...</div>
<script>
// Replace with your Flight Reader API secret key
const secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Note: You should never expose your API secret key like this in a production environment.
// Anyone who has access to it will be able to make API requests on your behalf.
// Call the "/v1/fields" endpoint to see a complete list of log fields that can be set here
const fieldName = "OSD.isGpsUsed";
async function retrieveLogField() {
try {
const response = await fetch(`https://api.flightreader.com/v1/fields/${fieldName}`, {
headers: {
"Authorization": `Bearer ${secretApiKey}`
}
});
if (response.ok) {
const data = await response.json();
output.textContent = JSON.stringify(data.result);
} else {
output.textContent = `${response.status} ${response.statusText}`;
}
} catch (error) {
output.textContent = error;
}
}
retrieveLogField();
</script>
</body>
</html>
<html>
<body>
<!-- https://github.com/brix/crypto-js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
<div id="output">Retrieving log field...</div>
<script>
// Replace with your Flight Reader API secret key
const publicApiKey = "pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Note: You should never expose your API secret key like this in a production environment.
// Anyone who has access to it will be able to make API requests on your behalf.
// Call the "/v1/fields" endpoint to see a complete list of log fields that can be set here
const fieldName = "OSD.isGpsUsed";
async function retrieveLogField() {
try {
// Create a request signature used by the Flight Reader API to verify the request was
// initiated by you. This signature will expire 5 minutes after it's created.
const signature = createSignature();
const response = await fetch(`https://api.flightreader.com/v1/fields/${fieldName}`, {
headers: {
"Authorization": `Bearer ${publicApiKey}`,
"Api-Signature": signature.value,
"Api-Timestamp": signature.currentUnixTimeInSeconds
}
});
if (response.ok) {
const data = await response.json();
output.textContent = JSON.stringify(data.result);
} else {
output.textContent = `${response.status} ${response.statusText}`;
}
} catch (error) {
output.textContent = error;
}
}
function createSignature() {
const currentUnixTimeInSeconds = Math.floor(Date.now() / 1000);
const requestMethod = "GET";
const requestResource = "/v1/fields/OSD.isGpsUsed";
const stringToSign = requestMethod + requestResource + currentUnixTimeInSeconds;
const value = CryptoJS.HmacSHA256(stringToSign, secretApiKey).toString(CryptoJS.enc.Hex);
return { value, currentUnixTimeInSeconds };
}
retrieveLogField();
</script>
</body>
</html>
using System.IO.Compression;
using System.Text;
internal class Program
{
static async Task Main()
{
// Replace with your Flight Reader API secret key
const string secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
using HttpClient client = new();
client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip");
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {secretApiKey}");
const string fieldName = "OSD.isGpsUsed";
var response = await client.GetAsync($"https://api.flightreader.com/v1/fields/{fieldName}");
if (response.IsSuccessStatusCode)
{
var responseBytes = await response.Content.ReadAsByteArrayAsync();
if (response.Content.Headers.ContentEncoding.Contains("gzip"))
{
using var compressedStream = new MemoryStream(responseBytes);
await using var decompressionStream = new GZipStream(compressedStream, CompressionMode.Decompress);
using var decompressedStream = new MemoryStream();
await decompressionStream.CopyToAsync(decompressedStream);
responseBytes = decompressedStream.ToArray();
}
var responseText = Encoding.UTF8.GetString(responseBytes);
Console.WriteLine(responseText);
}
else
{
Console.WriteLine($"Error: {response.StatusCode}");
}
}
}
using System.IO.Compression;
using System.Security.Cryptography;
using System.Text;
internal class Program
{
static async Task Main()
{
// Replace with your Flight Reader API keys
const string publicApiKey = "pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const string secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Create a request signature used by the Flight Reader API to verify the request was
// initiated by you. This signature will expire 5 minutes after it's created.
var currentUnixTimeInSeconds = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var requestMethod = "GET";
var requestResource = "/v1/fields/OSD.isGpsUsed";
var stringToSign = requestMethod + requestResource + currentUnixTimeInSeconds;
var signature = CreateSignature(stringToSign, secretApiKey);
using HttpClient client = new();
client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip");
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {publicApiKey}");
client.DefaultRequestHeaders.Add("Api-Signature", signature);
client.DefaultRequestHeaders.Add("Api-Timestamp", currentUnixTimeInSeconds.ToString());
const string fieldName = "OSD.isGpsUsed";
var response = client.GetAsync($"https://api.flightreader.com/v1/fields/{fieldName}").Result;
if (response.IsSuccessStatusCode)
{
var responseBytes = await response.Content.ReadAsByteArrayAsync();
if (response.Content.Headers.ContentEncoding.Contains("gzip"))
{
using var compressedStream = new MemoryStream(responseBytes);
await using var decompressionStream = new GZipStream(compressedStream, CompressionMode.Decompress);
using var decompressedStream = new MemoryStream();
await decompressionStream.CopyToAsync(decompressedStream);
responseBytes = decompressedStream.ToArray();
}
var responseText = Encoding.UTF8.GetString(responseBytes);
Console.WriteLine(responseText);
}
else
{
Console.WriteLine($"Error: {response.StatusCode}");
}
}
private static string CreateSignature(string value, string key)
{
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
var dataBytes = Encoding.UTF8.GetBytes(value);
var hashBytes = hmac.ComputeHash(dataBytes);
return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
}
}
{
"statusCode": 200,
"message": "GET Request successful.",
"result": {
"name": "OSD.isGpsUsed",
"description": "When true, the aircraft is able to use the GPS satellite data for positioning purposes."
}
}
Process a flight log file
POST /v1/logs
Upload a DJI TXT flight log or a ZIP containing a DJI TXT log. The file should be named like DJIFlightRecord_2022-05-16_[19-02-56].txt or DJIFlightRecord_2022-05-16_[19-02-56].zip.
Multipart Form Fields
- file file Required
The DJI TXT flight log or ZIP containing a DJI TXT flight log. Include the original filename in the multipart file part. - fields array of strings
Optional. Repeat the fields form field for each CSV column you want to include, using field names from GET /v1/fields. For best performance, specify only the flight log fields you need included in the returned CSV file. Omitting this field returns the default field set. Field availability depends on the telemetry data contained in the flight log.
Returns
Returns the processed flight log as a downloadable CSV file with a .csv filename and content type text/plain. The CSV contains the selected fields, or the default field set if none were supplied. Read the response as text or file bytes.
Privacy and Data Handling
Flight logs are processed entirely in memory and are retained only for the duration of processing. Neither the uploaded flight log file nor its processed contents are logged or permanently stored on Flight Reader API servers.
<html>
<body>
<input type="file" id="fileInput">
<br><br>
<div id="output"></div>
<script>
// Replace with your Flight Reader API secret key
const secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Note: You should never expose your API secret key like this in a production environment.
// Anyone who has access to it will be able to make API requests on your behalf.
// Call the "/v1/fields" endpoint to see a complete list of log fields that can be set here
const fields = ["OSD.flyTimeMilliseconds", "OSD.latitude", "OSD.longitude"];
async function processFlightLog() {
try {
const formData = new FormData();
formData.append("file", fileInput.files[0]);
// Remove this forEach loop to use the default field set
fields.forEach((value, index) => {
formData.append("fields", value);
});
const response = await fetch("https://api.flightreader.com/v1/logs", {
method: "POST",
body: formData,
headers: {
"Authorization": `Bearer ${secretApiKey}`
}
});
if (response.ok) {
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", "DJIFlightRecord.csv");
link.setAttribute("target", "_blank");
document.body.appendChild(link);
link.click();
output.textContent = "CSV file successfully downloaded";
} else {
const errorBody = await response.text();
output.textContent =
`${response.status} ${response.statusText}\n${errorBody}`;
}
} catch (error) {
output.textContent = error;
}
finally {
fileInput.value = null;
}
}
fileInput.addEventListener("change", () => {
output.textContent = "Processing flight log file...";
processFlightLog();
});
</script>
</body>
</html>
<html>
<body>
<!-- https://github.com/brix/crypto-js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
<input type="file" id="fileInput">
<br><br>
<div id="output"></div>
<script>
// Replace with your Flight Reader API keys
const publicApiKey = "pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Note: You should never expose your API secret key like this in a production environment.
// Anyone who has access to it will be able to make API requests on your behalf.
// Call the "/v1/fields" endpoint to see a complete list of log fields that can be set here
const fields = ["OSD.flyTimeMilliseconds", "OSD.latitude", "OSD.longitude"];
async function processFlightLog() {
try {
const formData = new FormData();
formData.append("file", fileInput.files[0]);
// Remove this forEach loop to use the default field set
fields.forEach((value, index) => {
formData.append("fields", value);
});
// Create a request signature used by the Flight Reader API to verify the request was
// initiated by you. This signature will expire 5 minutes after it's created.
const signature = createSignature(formData);
const response = await fetch("https://api.flightreader.com/v1/logs", {
method: "POST",
body: formData,
headers: {
"Authorization": `Bearer ${publicApiKey}`,
"Api-Signature": signature.value,
"Api-Timestamp": signature.currentUnixTimeInSeconds
}
});
if (response.ok) {
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", "DJIFlightRecord.csv");
link.setAttribute("target", "_blank");
document.body.appendChild(link);
link.click();
output.textContent = "CSV file successfully downloaded";
} else {
const errorBody = await response.text();
output.textContent =
`${response.status} ${response.statusText}\n${errorBody}`;
}
} catch (error) {
output.textContent = error;
}
finally {
fileInput.value = null;
}
}
function formDataToString(formData) {
const excludedKeys = new Set(["file"]);
const keyValuePairs = {};
for (const [key, value] of formData.entries()) {
if (excludedKeys.has(key)) {
continue;
}
if (!keyValuePairs[key]) {
keyValuePairs[key] = [value];
} else {
keyValuePairs[key].push(value);
}
}
return Object.entries(keyValuePairs)
.map(([key, values]) => `${key}=${values.join(",")}`)
.join("&");
}
function createSignature(formData) {
const currentUnixTimeInSeconds = Math.floor(Date.now() / 1000);
const requestMethod = "POST";
const requestResource = "/v1/logs";
const formDataString = formDataToString(formData);
const stringToSign = requestMethod + requestResource + formDataString + currentUnixTimeInSeconds;
const value = CryptoJS.HmacSHA256(stringToSign, secretApiKey).toString(CryptoJS.enc.Hex);
return { value, currentUnixTimeInSeconds };
}
fileInput.addEventListener("change", () => {
output.textContent = "Processing flight log file...";
processFlightLog();
});
</script>
</body>
</html>
using System.IO.Compression;
using System.Net.Http.Headers;
using System.Text;
internal class Program
{
static async Task Main()
{
// Replace with your Flight Reader API secret key
const string secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const string logFilePath = "C:\\DJIFlightRecord_2020-12-08_[13-39-22].txt";
var fields = new List<string>
{
"OSD.flyTimeMilliseconds",
"OSD.latitude",
"OSD.longitude"
};
var formContent = new MultipartFormDataContent();
// Add file to form
var fileContent = new ByteArrayContent(await File.ReadAllBytesAsync(logFilePath));
fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data")
{
Name = "file",
FileName = Path.GetFileName(logFilePath)
};
fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
formContent.Add(fileContent);
// Add fields to form (remove this foreach loop to use the default field set)
foreach (var field in fields)
{
formContent.Add(new StringContent(field), "fields");
}
// Process log
using HttpClient client = new();
client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip");
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {secretApiKey}");
var response = await client.PostAsync("https://api.flightreader.com/v1/logs", formContent);
var responseBytes = await response.Content.ReadAsByteArrayAsync();
if (response.Content.Headers.ContentEncoding.Contains("gzip"))
{
using var compressedStream = new MemoryStream(responseBytes);
await using var decompressionStream = new GZipStream(compressedStream, CompressionMode.Decompress);
using var decompressedStream = new MemoryStream();
await decompressionStream.CopyToAsync(decompressedStream);
responseBytes = decompressedStream.ToArray();
}
var responseText = Encoding.UTF8.GetString(responseBytes);
if (response.IsSuccessStatusCode)
{
// The successful response contains CSV data
Console.WriteLine(responseText);
}
else
{
Console.WriteLine($"Error: {(int)response.StatusCode} {response.ReasonPhrase}");
Console.WriteLine(responseText);
}
}
}
using System.IO.Compression;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
internal class Program
{
private static async Task Main()
{
// Replace with your Flight Reader API keys
const string publicApiKey = "pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const string secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const string logFilePath = "C:\\DJIFlightRecord_2020-12-08_[13-39-22].txt";
var fields = new List<string>
{
"OSD.flyTimeMilliseconds",
"OSD.latitude",
"OSD.longitude"
};
var formContent = new MultipartFormDataContent();
// Add file to form
var fileContent = new ByteArrayContent(await File.ReadAllBytesAsync(logFilePath));
fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data")
{
Name = "file",
FileName = Path.GetFileName(logFilePath)
};
fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
formContent.Add(fileContent);
// Add fields to form (remove this foreach loop to use the default field set)
foreach (var field in fields)
{
formContent.Add(new StringContent(field), "fields");
}
// Create a request signature used by the Flight Reader API to verify the request was
// initiated by you. This signature will expire 5 minutes after it's created.
var currentUnixTimeInSeconds = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var requestMethod = "POST";
var requestResource = "/v1/logs";
var formData = await GetFormDataAsync(formContent);
var stringToSign = $"{requestMethod}{requestResource}{formData}{currentUnixTimeInSeconds}";
var signature = CreateSignature(stringToSign, secretApiKey);
// Process log
using HttpClient client = new();
client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip");
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {publicApiKey}");
client.DefaultRequestHeaders.Add("Api-Signature", signature);
client.DefaultRequestHeaders.Add("Api-Timestamp", currentUnixTimeInSeconds.ToString());
var response = await client.PostAsync("https://api.flightreader.com/v1/logs", formContent);
var responseBytes = await response.Content.ReadAsByteArrayAsync();
if (response.Content.Headers.ContentEncoding.Contains("gzip"))
{
using var compressedStream = new MemoryStream(responseBytes);
await using var decompressionStream = new GZipStream(compressedStream, CompressionMode.Decompress);
using var decompressedStream = new MemoryStream();
await decompressionStream.CopyToAsync(decompressedStream);
responseBytes = decompressedStream.ToArray();
}
var responseText = Encoding.UTF8.GetString(responseBytes);
if (response.IsSuccessStatusCode)
{
// The successful response contains CSV data
Console.WriteLine(responseText);
}
else
{
Console.WriteLine($"Error: {(int)response.StatusCode} {response.ReasonPhrase}");
Console.WriteLine(responseText);
}
}
private static async Task<string> GetFormDataAsync(MultipartFormDataContent formContent)
{
var formData = "";
var lastFieldName = "";
foreach (var content in formContent)
{
if (content is not StringContent stringContent)
{
continue;
}
var fieldName = content.Headers.ContentDisposition?.Name?.Trim('"');
if (fieldName != lastFieldName)
{
lastFieldName = fieldName;
if (formData.Length > 0)
{
formData += "&";
}
formData += $"{fieldName}=";
}
else
{
formData += ",";
}
formData += await stringContent.ReadAsStringAsync();
}
return formData;
}
private static string CreateSignature(string value, string key)
{
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
var dataBytes = Encoding.UTF8.GetBytes(value);
var hashBytes = hmac.ComputeHash(dataBytes);
return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
}
}
OSD.flyTime [ms],OSD.latitude,OSD.longitude
100,53.62646476704494,16.87070193398017
300,53.62646474133018,16.87070199143842
500,53.62646469232834,16.870702037681247
...
Process a flight log URL
POST /v1/logs
To process a flight log using its URL, submit the complete downloadUrl returned by GET /v1/logs/{id} as the url multipart form field in a POST /v1/logs request.
This feature supports flight logs from DJI Cloud, DJI SmartFarm, and third-party websites. The API automatically downloads the flight log, processes its telemetry and metadata, and returns the results as a CSV file.
Multipart Form Fields
- url string Required
The complete flight log download URL or DJI SmartFarm processing reference. Use the complete downloadUrl returned by GET /v1/logs/{id}, or a supported third-party URL. Submit either url or file; an uploaded file takes precedence if both are provided. - fields array of strings
Optional. Repeat the fields form field for each CSV column you want to include, using names from GET /v1/fields. For best performance, specify only the flight log fields you need included in the returned CSV file. Omitting this field returns the default field set. Field availability depends on the telemetry data contained in the flight log.
Supported URLs
The following URLs can be used:
- DJI Cloud: Use the complete downloadUrl value returned by the GET /v1/logs/{id} endpoint.
- DJI SmartFarm: Use the complete downloadUrl value returned by the GET /v1/logs/{id} endpoint.
- Third-party websites: DJI TXT flight logs and ZIP files containing DJI TXT flight logs can also be downloaded from any publicly accessible third-party website.
Required Filename Format
All flight log filenames must follow the naming format DJIFlightRecord_YYYY-MM-DD_[HH-mm-ss] and have a .TXT or .ZIP file extension for DJI TXT flight logs or a .RAW file extension for DJI SmartFarm flight logs.
For example:
- DJIFlightRecord_2026-10-09_[14-30-00].txt
- DJIFlightRecord_2026-10-09_[14-30-00].zip
- DJIFlightRecord_2026-10-09_[14-30-00].raw
Returns
Returns the processed flight log as a downloadable CSV file with a .csv filename and content type text/plain. The CSV contains the selected fields, or the default field set if none were supplied. Read the response as text or file bytes. Returns 404 Not Found if the flight log cannot be downloaded.
Privacy and Data Handling
Flight logs are processed entirely in memory and are retained only for the duration of processing. Neither the original flight log nor its processed contents are logged or permanently stored on Flight Reader API servers.
<html>
<body>
<div id="output">Processing flight log...</div>
<script>
// Replace with your Flight Reader API secret key
const secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Note: You should never expose your API secret key like this in a production environment.
// Anyone who has access to it will be able to make API requests on your behalf.
// Set this URL to the complete downloadUrl returned by the "/v1/logs/{id}" endpoint
// for DJI Cloud or DJI SmartFarm flights. Alternatively, use a direct URL to a
// DJI TXT flight log hosted on your own website or another third-party website, like
// https://www.YourWebsite.com/DJIFlightRecord_2023-03-29_[12-28-24].txt
const flightLogUrl = "https://api.flightreader.com/v1/files/flight-logs/01234567890123456789/DJIFlightRecord_2023-03-29_[12-28-24].txt";
// Call the "/v1/fields" endpoint to see a complete list of log fields that can be set here
const fields = ["OSD.flyTimeMilliseconds", "OSD.latitude", "OSD.longitude"];
async function processFlightLog() {
try {
const output = document.getElementById("output");
const formData = new FormData();
formData.append("url", flightLogUrl);
// Remove this forEach loop to use the default field set
fields.forEach((value, index) => {
formData.append("fields", value);
});
const response = await fetch("https://api.flightreader.com/v1/logs", {
method: "POST",
body: formData,
headers: {
"Authorization": `Bearer ${secretApiKey}`
}
});
if (response.ok) {
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", "DJIFlightRecord.csv");
link.setAttribute("target", "_blank");
document.body.appendChild(link);
link.click();
output.textContent = "CSV file successfully downloaded";
} else {
const errorBody = await response.text();
output.textContent =
`${response.status} ${response.statusText}\n${errorBody}`;
}
} catch (error) {
output.textContent = error;
}
}
processFlightLog();
</script>
</body>
</html>
<html>
<body>
<!-- https://github.com/brix/crypto-js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
<div id="output">Processing flight log...</div>
<script>
// Replace with your Flight Reader API keys
const publicApiKey = "pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Note: You should never expose your API secret key like this in a production environment.
// Anyone who has access to it will be able to make API requests on your behalf.
// Set this URL to the complete downloadUrl returned by GET /v1/logs/{id} (Cloud or SmartFarm)
const flightLogUrl = "https://www.YourWebsite.com/DJIFlightRecord_2020-05-16_[19-02-56].txt";
// Call the "/v1/fields" endpoint to see a complete list of log fields that can be set here
const fields = ["OSD.flyTimeMilliseconds", "OSD.latitude", "OSD.longitude"];
async function processFlightLog() {
try {
const formData = new FormData();
formData.append("url", flightLogUrl);
// Remove this forEach loop to use the default field set
fields.forEach((value, index) => {
formData.append("fields", value);
});
// Create a request signature used by the Flight Reader API to verify the request was
// initiated by you. This signature will expire 5 minutes after it's created.
const signature = createSignature(formData);
const response = await fetch("https://api.flightreader.com/v1/logs", {
method: "POST",
body: formData,
headers: {
"Authorization": `Bearer ${publicApiKey}`,
"Api-Signature": signature.value,
"Api-Timestamp": signature.currentUnixTimeInSeconds
}
});
if (response.ok) {
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", "DJIFlightRecord.csv");
link.setAttribute("target", "_blank");
document.body.appendChild(link);
link.click();
output.textContent = "CSV file successfully downloaded";
} else {
const errorBody = await response.text();
output.textContent =
`${response.status} ${response.statusText}\n${errorBody}`;
}
} catch (error) {
output.textContent = error;
}
}
function formDataToString(formData) {
const keyValuePairs = {};
for (const [key, value] of formData.entries()) {
if (!keyValuePairs[key]) {
keyValuePairs[key] = [value];
} else {
keyValuePairs[key].push(value);
}
}
return Object.entries(keyValuePairs)
.map(([key, values]) => `${key}=${values.join(",")}`)
.join("&");
}
function createSignature(formData) {
const currentUnixTimeInSeconds = Math.floor(Date.now() / 1000);
const requestMethod = "POST";
const requestResource = "/v1/logs";
const formDataString = formDataToString(formData);
const stringToSign = requestMethod + requestResource + formDataString + currentUnixTimeInSeconds;
const value = CryptoJS.HmacSHA256(stringToSign, secretApiKey).toString(CryptoJS.enc.Hex);
return { value, currentUnixTimeInSeconds };
}
processFlightLog();
</script>
</body>
</html>
using System.IO.Compression;
using System.Text;
internal class Program
{
private static async Task Main()
{
// Replace with your Flight Reader API secret key
const string secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const string logFileUrl = "https://www.YourWebsite.com/DJIFlightRecord_2020-05-16_[19-02-56].txt";
var fields = new List<string>
{
"OSD.flyTimeMilliseconds",
"OSD.latitude",
"OSD.longitude"
};
var formContent = new MultipartFormDataContent();
// Add url to form
formContent.Add(new StringContent(logFileUrl), "url");
// Add fields to form (remove this foreach loop to use the default field set)
foreach (var field in fields)
{
formContent.Add(new StringContent(field), "fields");
}
// Process log
using HttpClient client = new();
client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip");
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {secretApiKey}");
var response = await client.PostAsync("https://api.flightreader.com/v1/logs", formContent);
var responseBytes = await response.Content.ReadAsByteArrayAsync();
if (response.Content.Headers.ContentEncoding.Contains("gzip"))
{
using var compressedStream = new MemoryStream(responseBytes);
await using var decompressionStream = new GZipStream(compressedStream, CompressionMode.Decompress);
using var decompressedStream = new MemoryStream();
await decompressionStream.CopyToAsync(decompressedStream);
responseBytes = decompressedStream.ToArray();
}
var responseText = Encoding.UTF8.GetString(responseBytes);
if (response.IsSuccessStatusCode)
{
// The successful response contains CSV data
Console.WriteLine(responseText);
}
else
{
Console.WriteLine($"Error: {(int)response.StatusCode} {response.ReasonPhrase}");
Console.WriteLine(responseText);
}
}
}
using System.IO.Compression;
using System.Security.Cryptography;
using System.Text;
internal class Program
{
private static async Task Main()
{
// Replace with your Flight Reader API keys
const string publicApiKey = "pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const string secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const string logFileUrl = "https://www.YourWebsite.com/DJIFlightRecord_2020-05-16_[19-02-56].txt";
var fields = new List<string>
{
"OSD.flyTimeMilliseconds",
"OSD.latitude",
"OSD.longitude"
};
var formContent = new MultipartFormDataContent();
// Add url to form
formContent.Add(new StringContent(logFileUrl), "url");
// Add fields to form (remove this foreach loop to use the default field set)
foreach (var field in fields)
{
formContent.Add(new StringContent(field), "fields");
}
// Create a request signature used by the Flight Reader API to verify the request was
// initiated by you. This signature will expire 5 minutes after it's created.
var currentUnixTimeInSeconds = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var requestMethod = "POST";
var requestResource = "/v1/logs";
var formData = await GetFormDataAsync(formContent);
var stringToSign = requestMethod + requestResource + formData + currentUnixTimeInSeconds;
var signature = CreateSignature(stringToSign, secretApiKey);
// Process log
using HttpClient client = new();
client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip");
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {publicApiKey}");
client.DefaultRequestHeaders.Add("Api-Signature", signature);
client.DefaultRequestHeaders.Add("Api-Timestamp", currentUnixTimeInSeconds.ToString());
var response = await client.PostAsync("https://api.flightreader.com/v1/logs", formContent);
var responseBytes = await response.Content.ReadAsByteArrayAsync();
if (response.Content.Headers.ContentEncoding.Contains("gzip"))
{
using var compressedStream = new MemoryStream(responseBytes);
await using var decompressionStream = new GZipStream(compressedStream, CompressionMode.Decompress);
using var decompressedStream = new MemoryStream();
await decompressionStream.CopyToAsync(decompressedStream);
responseBytes = decompressedStream.ToArray();
}
var responseText = Encoding.UTF8.GetString(responseBytes);
if (response.IsSuccessStatusCode)
{
// The successful response contains CSV data
Console.WriteLine(responseText);
}
else
{
Console.WriteLine($"Error: {(int)response.StatusCode} {response.ReasonPhrase}");
Console.WriteLine(responseText);
}
}
private static async Task<string> GetFormDataAsync(MultipartFormDataContent formContent)
{
var formData = "";
var lastFieldName = "";
foreach (var content in formContent)
{
if (content is not StringContent stringContent)
{
continue;
}
var fieldName = content.Headers.ContentDisposition?.Name?.Trim('"');
if (fieldName != lastFieldName)
{
lastFieldName = fieldName;
if (formData.Length > 0)
{
formData += "&";
}
formData += $"{fieldName}=";
}
else
{
formData += ",";
}
formData += await stringContent.ReadAsStringAsync();
}
return formData;
}
private static string CreateSignature(string value, string key)
{
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
var dataBytes = Encoding.UTF8.GetBytes(value);
var hashBytes = hmac.ComputeHash(dataBytes);
return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
}
}
OSD.flyTime [ms],OSD.latitude,OSD.longitude
100,53.62646476704494,16.87070193398017
300,53.62646474133018,16.87070199143842
500,53.62646469232834,16.870702037681247
...
Retrieve DJI Cloud account
POST /v1/accounts/dji/cloud
Retrieves the unique ID associated with a DJI Cloud account. Submit the DJI account's email and password as multipart form fields. The returned result.id can then be used with GET /v1/logs/{id} to retrieve the account's flight logs.
Multipart Form Fields
- email string Required
The email address for the DJI Cloud account. - password string Required
The password for the DJI Cloud account.
Credential Storage and Token Renewal
For security, the DJI account's email, password, and authentication token are stored together in AWS Systems Manager Parameter Store as an encrypted SecureString.
If a DJI request fails because its authentication token is invalid or expired, the API automatically uses the stored credentials to obtain a new token and retries the failed request once.
If authentication or the retry fails, you can reconnect the DJI Cloud account by submitting its current email and password to this endpoint again. The existing account ID is preserved, so there's no need to update any references to that account in your application.
Returns
Returns a JSON response with the DJI Cloud account ID in result.id. Use this ID to retrieve the account's flight log list. If the account is already registered, the existing ID is returned.
<html>
<body>
<div id="output">Retrieving DJI account id...</div>
<script>
// Replace with your Flight Reader API secret key
const secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Note: You should never expose your API secret key like this in a production environment.
// Anyone who has access to it will be able to make API requests on your behalf.
// Set the DJI account credentials
const email = "[email protected]";
const password = "abc123";
const formData = new FormData();
formData.append("email", email);
formData.append("password", password);
async function retrieveDjiAccount() {
try {
const response = await fetch("https://api.flightreader.com/v1/accounts/dji/cloud", {
method: "POST",
body: formData,
headers: {
"Authorization": `Bearer ${secretApiKey}`
}
});
if (response.ok) {
const data = await response.json();
output.textContent = JSON.stringify(data.result);
} else {
output.textContent = `${response.status} ${response.statusText}`;
}
} catch (error) {
output.textContent = error;
}
}
retrieveDjiAccount();
</script>
</body>
</html>
<html>
<body>
<!-- https://github.com/brix/crypto-js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
<div id="output">Retrieving DJI account id...</div>
<script>
// Replace with your Flight Reader API keys
const publicApiKey = "pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Note: You should never expose your API secret key like this in a production environment.
// Anyone who has access to it will be able to make API requests on your behalf.
// Set the DJI account credentials
const email = "[email protected]";
const password = "abc123";
async function retrieveDjiAccount() {
try {
const formData = new FormData();
formData.append("email", email);
formData.append("password", password);
// Create a request signature used by the Flight Reader API to verify the request was
// initiated by you. This signature will expire 5 minutes after it's created.
const signature = createSignature(formData);
const response = await fetch("https://api.flightreader.com/v1/accounts/dji/cloud", {
method: "POST",
body: formData,
headers: {
"Authorization": `Bearer ${publicApiKey}`,
"Api-Signature": signature.value,
"Api-Timestamp": signature.currentUnixTimeInSeconds
}
});
if (response.ok) {
const data = await response.json();
output.textContent = JSON.stringify(data.result);
} else {
output.textContent = `${response.status} ${response.statusText}`;
}
} catch (error) {
output.textContent = error;
}
}
function formDataToString(formData) {
const keyValuePairs = {};
for (const [key, value] of formData.entries()) {
if (!keyValuePairs[key]) {
keyValuePairs[key] = [value];
} else {
keyValuePairs[key].push(value);
}
}
return Object.entries(keyValuePairs)
.map(([key, values]) => `${key}=${values.join(",")}`)
.join("&");
}
function createSignature(formData) {
const currentUnixTimeInSeconds = Math.floor(Date.now() / 1000);
const requestMethod = "POST";
const requestResource = "/v1/accounts/dji/cloud";
const formDataString = formDataToString(formData);
const stringToSign = requestMethod + requestResource + formDataString + currentUnixTimeInSeconds;
const value = CryptoJS.HmacSHA256(stringToSign, secretApiKey).toString(CryptoJS.enc.Hex);
return { value, currentUnixTimeInSeconds };
}
retrieveDjiAccount();
</script>
</body>
</html>
using System.IO.Compression;
using System.Text;
internal class Program
{
private static async Task Main()
{
// Replace with your Flight Reader API secret key
const string secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Set the DJI account credentials
const string email = "[email protected]";
const string password = "abc123";
var formContent = new MultipartFormDataContent
{
{ new StringContent(email), "email" },
{ new StringContent(password), "password" }
};
using HttpClient client = new();
client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip");
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {secretApiKey}");
var response = await client.PostAsync("https://api.flightreader.com/v1/accounts/dji/cloud", formContent);
if (response.IsSuccessStatusCode)
{
var responseBytes = await response.Content.ReadAsByteArrayAsync();
if (response.Content.Headers.ContentEncoding.Contains("gzip"))
{
using var compressedStream = new MemoryStream(responseBytes);
await using var decompressionStream = new GZipStream(compressedStream, CompressionMode.Decompress);
using var decompressedStream = new MemoryStream();
await decompressionStream.CopyToAsync(decompressedStream);
responseBytes = decompressedStream.ToArray();
}
var responseText = Encoding.UTF8.GetString(responseBytes);
Console.WriteLine(responseText);
}
else
{
Console.WriteLine($"Error: {response.StatusCode}");
}
}
}
using System.IO.Compression;
using System.Security.Cryptography;
using System.Text;
internal class Program
{
private static async Task Main()
{
// Replace with your Flight Reader API keys
const string publicApiKey = "pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const string secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Set the DJI account credentials
const string email = "[email protected]";
const string password = "abc123";
var formContent = new MultipartFormDataContent
{
{ new StringContent(email), "email" },
{ new StringContent(password), "password" }
};
// Create a request signature used by the Flight Reader API to verify the request was
// initiated by you. This signature will expire 5 minutes after it's created.
var currentUnixTimeInSeconds = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var requestMethod = "POST";
var requestResource = "/v1/accounts/dji/cloud";
var formData = await GetFormDataAsync(formContent);
var stringToSign = requestMethod + requestResource + formData + currentUnixTimeInSeconds;
var signature = CreateSignature(stringToSign, secretApiKey);
using HttpClient client = new();
client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip");
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {publicApiKey}");
client.DefaultRequestHeaders.Add("Api-Signature", signature);
client.DefaultRequestHeaders.Add("Api-Timestamp", currentUnixTimeInSeconds.ToString());
var response = await client.PostAsync("https://api.flightreader.com/v1/accounts/dji/cloud", formContent);
if (response.IsSuccessStatusCode)
{
var responseBytes = await response.Content.ReadAsByteArrayAsync();
if (response.Content.Headers.ContentEncoding.Contains("gzip"))
{
using var compressedStream = new MemoryStream(responseBytes);
await using var decompressionStream = new GZipStream(compressedStream, CompressionMode.Decompress);
using var decompressedStream = new MemoryStream();
await decompressionStream.CopyToAsync(decompressedStream);
responseBytes = decompressedStream.ToArray();
}
var responseText = Encoding.UTF8.GetString(responseBytes);
Console.WriteLine(responseText);
}
else
{
Console.WriteLine($"Error: {response.StatusCode}");
}
}
private static async Task<string> GetFormDataAsync(MultipartFormDataContent formContent)
{
var formData = "";
var lastFieldName = "";
foreach (var content in formContent)
{
if (content is not StringContent stringContent)
{
continue;
}
var fieldName = content.Headers.ContentDisposition?.Name?.Trim('"');
if (fieldName != lastFieldName)
{
lastFieldName = fieldName;
if (formData.Length > 0)
{
formData += "&";
}
formData += $"{fieldName}=";
}
else
{
formData += ",";
}
formData += await stringContent.ReadAsStringAsync();
}
return formData;
}
private static string CreateSignature(string value, string key)
{
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
var dataBytes = Encoding.UTF8.GetBytes(value);
var hashBytes = hmac.ComputeHash(dataBytes);
return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
}
}
{
"statusCode": 200,
"message": "POST Request successful.",
"result": {
"id": "a014f2df954b87acd647918c73a9cffc"
}
}
Retrieve DJI SmartFarm account
POST /v1/accounts/dji/smartfarm
Retrieves the unique ID associated with a DJI SmartFarm account. Submit the DJI account's email and password as multipart form fields. The returned result.id can then be used with GET /v1/logs/{id} to retrieve the account's flight logs.
Multipart Form Fields
- email string Required
The email address for the DJI SmartFarm account. - password string Required
The password for the DJI SmartFarm account.
Credential Storage and Token Renewal
For security, the DJI account's email, password, and authentication token are stored together in AWS Systems Manager Parameter Store as an encrypted SecureString.
If a DJI request fails because its authentication token is invalid or expired, the API automatically uses the stored credentials to obtain a new token and retries the failed request once.
If authentication or the retry fails, you can reconnect the DJI SmartFarm account by submitting its current email and password to this endpoint again. The existing account ID is preserved, so there's no need to update any references to that account in your application.
Returns
Returns a JSON response with the DJI SmartFarm account ID in result.id. Use this ID to retrieve the account's flight log list. If the account is already registered, the existing ID is returned.
<html>
<body>
<div id="output">Retrieving DJI account id...</div>
<script>
// Replace with your Flight Reader API secret key
const secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Note: You should never expose your API secret key like this in a production environment.
// Anyone who has access to it will be able to make API requests on your behalf.
// Set the DJI account credentials
const email = "[email protected]";
const password = "abc123";
const formData = new FormData();
formData.append("email", email);
formData.append("password", password);
async function retrieveDjiAccount() {
try {
const response = await fetch("https://api.flightreader.com/v1/accounts/dji/smartfarm", {
method: "POST",
body: formData,
headers: {
"Authorization": `Bearer ${secretApiKey}`
}
});
if (response.ok) {
const data = await response.json();
output.textContent = JSON.stringify(data.result);
} else {
output.textContent = `${response.status} ${response.statusText}`;
}
} catch (error) {
output.textContent = error;
}
}
retrieveDjiAccount();
</script>
</body>
</html>
<html>
<body>
<!-- https://github.com/brix/crypto-js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
<div id="output">Retrieving DJI account id...</div>
<script>
// Replace with your Flight Reader API keys
const publicApiKey = "pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Note: You should never expose your API secret key like this in a production environment.
// Anyone who has access to it will be able to make API requests on your behalf.
// Set the DJI account credentials
const email = "[email protected]";
const password = "abc123";
async function retrieveDjiAccount() {
try {
const formData = new FormData();
formData.append("email", email);
formData.append("password", password);
// Create a request signature used by the Flight Reader API to verify the request was
// initiated by you. This signature will expire 5 minutes after it's created.
const signature = createSignature(formData);
const response = await fetch("https://api.flightreader.com/v1/accounts/dji/smartfarm", {
method: "POST",
body: formData,
headers: {
"Authorization": `Bearer ${publicApiKey}`,
"Api-Signature": signature.value,
"Api-Timestamp": signature.currentUnixTimeInSeconds
}
});
if (response.ok) {
const data = await response.json();
output.textContent = JSON.stringify(data.result);
} else {
output.textContent = `${response.status} ${response.statusText}`;
}
} catch (error) {
output.textContent = error;
}
}
function formDataToString(formData) {
const keyValuePairs = {};
for (const [key, value] of formData.entries()) {
if (!keyValuePairs[key]) {
keyValuePairs[key] = [value];
} else {
keyValuePairs[key].push(value);
}
}
return Object.entries(keyValuePairs)
.map(([key, values]) => `${key}=${values.join(",")}`)
.join("&");
}
function createSignature(formData) {
const currentUnixTimeInSeconds = Math.floor(Date.now() / 1000);
const requestMethod = "POST";
const requestResource = "/v1/accounts/dji/smartfarm";
const formDataString = formDataToString(formData);
const stringToSign = requestMethod + requestResource + formDataString + currentUnixTimeInSeconds;
const value = CryptoJS.HmacSHA256(stringToSign, secretApiKey).toString(CryptoJS.enc.Hex);
return { value, currentUnixTimeInSeconds };
}
retrieveDjiAccount();
</script>
</body>
</html>
using System.IO.Compression;
using System.Text;
internal class Program
{
private static async Task Main()
{
// Replace with your Flight Reader API secret key
const string secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Set the DJI account credentials
const string email = "[email protected]";
const string password = "abc123";
var formContent = new MultipartFormDataContent
{
{ new StringContent(email), "email" },
{ new StringContent(password), "password" }
};
using HttpClient client = new();
client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip");
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {secretApiKey}");
var response = await client.PostAsync("https://api.flightreader.com/v1/accounts/dji/smartfarm", formContent);
if (response.IsSuccessStatusCode)
{
var responseBytes = await response.Content.ReadAsByteArrayAsync();
if (response.Content.Headers.ContentEncoding.Contains("gzip"))
{
using var compressedStream = new MemoryStream(responseBytes);
await using var decompressionStream = new GZipStream(compressedStream, CompressionMode.Decompress);
using var decompressedStream = new MemoryStream();
await decompressionStream.CopyToAsync(decompressedStream);
responseBytes = decompressedStream.ToArray();
}
var responseText = Encoding.UTF8.GetString(responseBytes);
Console.WriteLine(responseText);
}
else
{
Console.WriteLine($"Error: {response.StatusCode}");
}
}
}
using System.IO.Compression;
using System.Security.Cryptography;
using System.Text;
internal class Program
{
private static async Task Main()
{
// Replace with your Flight Reader API keys
const string publicApiKey = "pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const string secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Set the DJI account credentials
const string email = "[email protected]";
const string password = "abc123";
var formContent = new MultipartFormDataContent
{
{ new StringContent(email), "email" },
{ new StringContent(password), "password" }
};
// Create a request signature used by the Flight Reader API to verify the request was
// initiated by you. This signature will expire 5 minutes after it's created.
var currentUnixTimeInSeconds = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var requestMethod = "POST";
var requestResource = "/v1/accounts/dji/smartfarm";
var formData = await GetFormDataAsync(formContent);
var stringToSign = requestMethod + requestResource + formData + currentUnixTimeInSeconds;
var signature = CreateSignature(stringToSign, secretApiKey);
using HttpClient client = new();
client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip");
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {publicApiKey}");
client.DefaultRequestHeaders.Add("Api-Signature", signature);
client.DefaultRequestHeaders.Add("Api-Timestamp", currentUnixTimeInSeconds.ToString());
var response = await client.PostAsync("https://api.flightreader.com/v1/accounts/dji/smartfarm", formContent);
if (response.IsSuccessStatusCode)
{
var responseBytes = await response.Content.ReadAsByteArrayAsync();
if (response.Content.Headers.ContentEncoding.Contains("gzip"))
{
using var compressedStream = new MemoryStream(responseBytes);
await using var decompressionStream = new GZipStream(compressedStream, CompressionMode.Decompress);
using var decompressedStream = new MemoryStream();
await decompressionStream.CopyToAsync(decompressedStream);
responseBytes = decompressedStream.ToArray();
}
var responseText = Encoding.UTF8.GetString(responseBytes);
Console.WriteLine(responseText);
}
else
{
Console.WriteLine($"Error: {response.StatusCode}");
}
}
private static async Task<string> GetFormDataAsync(MultipartFormDataContent formContent)
{
var formData = "";
var lastFieldName = "";
foreach (var content in formContent)
{
if (content is not StringContent stringContent)
{
continue;
}
var fieldName = content.Headers.ContentDisposition?.Name?.Trim('"');
if (fieldName != lastFieldName)
{
lastFieldName = fieldName;
if (formData.Length > 0)
{
formData += "&";
}
formData += $"{fieldName}=";
}
else
{
formData += ",";
}
formData += await stringContent.ReadAsStringAsync();
}
return formData;
}
private static string CreateSignature(string value, string key)
{
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
var dataBytes = Encoding.UTF8.GetBytes(value);
var hashBytes = hmac.ComputeHash(dataBytes);
return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
}
}
{
"statusCode": 200,
"message": "POST Request successful.",
"result": {
"id": "a014f2df954b87acd647918c73a9cffc"
}
}
Retrieve flight log list
GET /v1/logs/{id}
Retrieves flight logs from DJI Cloud or SmartFarm, based on the account id returned by POST /v1/accounts/dji/cloud or POST /v1/accounts/dji/smartfarm.
Pass the complete downloadUrl to POST /v1/logs to obtain CSV.
Route Parameters
- id string Required
Replace {id} with the account ID returned as result.id by POST /v1/accounts/dji/cloud or POST /v1/accounts/dji/smartfarm.
URL Parameters
- limit integer
The maximum number of flights to return. The default is 200. - createdAfterId integer
Returns flights with an ID greater than the specified value. Set to 0 to retrieve the oldest available flights, up to the specified limit. For incremental imports, use the highest flight ID returned in the previous batch. - createdAfterTimestamp integer
Returns flights created after the specified timestamp. For DJI Cloud accounts, provide the timestamp in Unix milliseconds. For DJI SmartFarm accounts, provide the timestamp in Unix seconds.
Returns
Returns a JSON response with an array of flight logs in result. Each entry includes id, fileName, downloadUrl, aircraftName, aircraftSerialNumber, timestamp, md5Hash, and available flight statistics: maxHeight, totalDistance, and totalTime. Use the complete downloadUrl to download or process a flight log.
<html>
<body>
<div id="output">Retrieving flight log list...</div>
<script>
// Replace with your Flight Reader API secret key
const secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Note: You should never expose your API secret key like this in a production environment.
// Anyone who has access to it will be able to make API requests on your behalf.
// Use /v1/accounts/dji/cloud or /v1/accounts/dji/smartfarm to retrieve this account id
const id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
async function retrieveFlightLogList() {
try {
// The optional "limit" and "createdAfterId" parameters can be appended to the URL
// like this:
//
// https://api.flightreader.com/v1/logs/${id}?limit=20&createdAfterId=5926331
const response = await fetch(`https://api.flightreader.com/v1/logs/${id}`, {
headers: {
"Authorization": `Bearer ${secretApiKey}`
}
});
if (response.ok) {
const data = await response.json();
output.textContent = JSON.stringify(data.result);
} else {
output.textContent = `${response.status} ${response.statusText}`;
}
} catch (error) {
output.textContent = error;
}
}
retrieveFlightLogList();
</script>
</body>
</html>
<html>
<body>
<!-- https://github.com/brix/crypto-js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
<div id="output">Retrieving flight log list...</div>
<script>
// Replace with your Flight Reader API keys
const publicApiKey = "pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Note: You should never expose your API secret key like this in a production environment.
// Anyone who has access to it will be able to make API requests on your behalf.
// Use /v1/accounts/dji/cloud or /v1/accounts/dji/smartfarm to retrieve this account id
const id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
async function retrieveFlightLogList() {
try {
// Create a request signature used by the Flight Reader API to verify the request was
// initiated by you. This signature will expire 5 minutes after it's created.
const signature = createSignature();
// The optional "limit" and "createdAfterId" parameters can be appended to the URL
// like this:
//
// https://api.flightreader.com/v1/logs/${id}?limit=20&createdAfterId=5926331
const response = await fetch(`https://api.flightreader.com/v1/logs/${id}`, {
headers: {
"Authorization": `Bearer ${publicApiKey}`,
"Api-Signature": signature.value,
"Api-Timestamp": signature.currentUnixTimeInSeconds
}
});
if (response.ok) {
const data = await response.json();
output.textContent = JSON.stringify(data.result);
} else {
output.textContent = `${response.status} ${response.statusText}`;
}
} catch (error) {
output.textContent = error;
}
}
function createSignature() {
const currentUnixTimeInSeconds = Math.floor(Date.now() / 1000);
const requestMethod = "GET";
const requestResource = `/v1/logs/${id}`;
const stringToSign = requestMethod + requestResource + currentUnixTimeInSeconds;
const value = CryptoJS.HmacSHA256(stringToSign, secretApiKey).toString(CryptoJS.enc.Hex);
return { value, currentUnixTimeInSeconds };
}
retrieveFlightLogList();
</script>
</body>
</html>
using System.IO.Compression;
using System.Text;
internal class Program
{
private static async Task Main()
{
// Replace with your Flight Reader API secret key
const string secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Use /v1/accounts/dji/cloud or /v1/accounts/dji/smartfarm to retrieve this account id
const string id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// The optional "limit" and "createdAfterId" parameters can be appended to the URL
// like this:
//
// var response = await client.GetAsync($"https://api.flightreader.com/v1/logs/{id}?limit=20&createdAfterId=5926331");
using HttpClient client = new();
client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip");
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {secretApiKey}");
var response = await client.GetAsync($"https://api.flightreader.com/v1/logs/{id}");
if (response.IsSuccessStatusCode)
{
var responseBytes = await response.Content.ReadAsByteArrayAsync();
if (response.Content.Headers.ContentEncoding.Contains("gzip"))
{
using var compressedStream = new MemoryStream(responseBytes);
await using var decompressionStream = new GZipStream(compressedStream, CompressionMode.Decompress);
using var decompressedStream = new MemoryStream();
await decompressionStream.CopyToAsync(decompressedStream);
responseBytes = decompressedStream.ToArray();
}
var responseText = Encoding.UTF8.GetString(responseBytes);
Console.WriteLine(responseText);
}
else
{
Console.WriteLine($"Error: {response.StatusCode}");
}
}
}
using System.IO.Compression;
using System.Security.Cryptography;
using System.Text;
internal class Program
{
private static async Task Main()
{
// Replace with your Flight Reader API keys
const string publicApiKey = "pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const string secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Use /v1/accounts/dji/cloud or /v1/accounts/dji/smartfarm to retrieve this account id
const string id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// The optional "limit" and "createdAfterId" parameters can be appended to the URL
// like this:
//
// var response = await client.GetAsync($"https://api.flightreader.com/v1/logs/{id}?limit=20&createdAfterId=5926331");
// Create a request signature used by the Flight Reader API to verify the request was
// initiated by you. This signature will expire 5 minutes after it's created.
var currentUnixTimeInSeconds = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var requestMethod = "GET";
var requestResource = $"/v1/logs/{id}";
var stringToSign = requestMethod + requestResource + currentUnixTimeInSeconds;
var signature = CreateSignature(stringToSign, secretApiKey);
using HttpClient client = new();
client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip");
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {publicApiKey}");
client.DefaultRequestHeaders.Add("Api-Signature", signature);
client.DefaultRequestHeaders.Add("Api-Timestamp", currentUnixTimeInSeconds.ToString());
var response = await client.GetAsync($"https://api.flightreader.com/v1/logs/{id}");
if (response.IsSuccessStatusCode)
{
var responseBytes = await response.Content.ReadAsByteArrayAsync();
if (response.Content.Headers.ContentEncoding.Contains("gzip"))
{
using var compressedStream = new MemoryStream(responseBytes);
await using var decompressionStream = new GZipStream(compressedStream, CompressionMode.Decompress);
using var decompressedStream = new MemoryStream();
await decompressionStream.CopyToAsync(decompressedStream);
responseBytes = decompressedStream.ToArray();
}
var responseText = Encoding.UTF8.GetString(responseBytes);
Console.WriteLine(responseText);
}
else
{
Console.WriteLine($"Error: {response.StatusCode}");
}
}
private static string CreateSignature(string value, string key)
{
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
var dataBytes = Encoding.UTF8.GetBytes(value);
var hashBytes = hmac.ComputeHash(dataBytes);
return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
}
}
{
"statusCode": 200,
"message": "GET Request successful.",
"result": [
{
"aircraftName": "Mavic 3",
"aircraftSerialNumber": "125A845TA35AF2AG0220",
"downloadUrl": "https://api.flightreader.com/v1/files/flight-logs/01234567890123456789/DJIFlightRecord_2023-03-29_[12-28-24].txt",
"fileName": "DJIFlightRecord_2023-03-29_[12-28-24].txt",
"id": 5926331,
"maxHeight": 65,
"md5Hash": "436412e045c9ad2db38c18cfc4cb0149",
"timestamp": 1680107304788,
"totalDistance": 1485.5077,
"totalTime": 165400
}
]
}
Download a DJI Cloud flight log
GET /v1/files/flight-logs/{id}/{filename}
Downloads the original DJI TXT flight log from DJI Cloud. Use the complete downloadUrl returned by GET /v1/logs/{id} to request the download. The URL provides download authorization, so a separate Bearer header is not required. Keep the URL private.
Route Parameters
- id string Required
The download authorization identifier embedded in the returned downloadUrl. Preserve this value exactly; do not substitute an account ID or flight ID. - filename string Required
The original DJI TXT flight log filename embedded in the returned downloadUrl. Preserve the filename and its URL encoding exactly.
Returns
Returns a downloadable ZIP file containing the original DJI TXT flight log, with content type application/zip and a .zip attachment filename. Read the response as file bytes. Returns 404 Not Found if the download is invalid or unavailable.
Retrieve API usage
GET /v1/usage
Retrieves a list of billable API requests made by your API account. Returns usage across all API keys belonging to your account.
URL Parameters
-
limit integer
Sets the maximum number of days to retrieve. The default value is 30.
Returns
Returns a JSON response with account-wide usage in result for the requested number of days. It includes totalRequests, counts grouped by API key in requestsByKey, and daily totals and request counts in requestsByDate.
<html>
<body>
<div id="output">Retrieving API usage...</div>
<script>
// Replace with your Flight Reader API secret key
const secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Note: You should never expose your API secret key like this in a production environment.
// Anyone who has access to it will be able to make API requests on your behalf.
async function retrieveApiUsage() {
try {
// The optional "limit" parameter can be appended to the URL like this:
//
// https://api.flightreader.com/v1/usage?limit=7
const response = await fetch(`https://api.flightreader.com/v1/usage`, {
headers: {
"Authorization": `Bearer ${secretApiKey}`
}
});
if (response.ok) {
const data = await response.json();
output.textContent = JSON.stringify(data.result);
} else {
output.textContent = `${response.status} ${response.statusText}`;
}
} catch (error) {
output.textContent = error;
}
}
retrieveApiUsage();
</script>
</body>
</html>
using System.IO.Compression;
using System.Text;
internal class Program
{
private static async Task Main()
{
// Replace with your Flight Reader API secret key
const string secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
using HttpClient client = new();
client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip");
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {secretApiKey}");
// The optional "limit" parameter can be appended to the URL like this:
//
// https://api.flightreader.com/v1/usage?limit=7
var response = await client.GetAsync("https://api.flightreader.com/v1/usage");
if (response.IsSuccessStatusCode)
{
var responseBytes = await response.Content.ReadAsByteArrayAsync();
if (response.Content.Headers.ContentEncoding.Contains("gzip"))
{
using var compressedStream = new MemoryStream(responseBytes);
await using var decompressionStream = new GZipStream(compressedStream, CompressionMode.Decompress);
using var decompressedStream = new MemoryStream();
await decompressionStream.CopyToAsync(decompressedStream);
responseBytes = decompressedStream.ToArray();
}
var responseText = Encoding.UTF8.GetString(responseBytes);
Console.WriteLine(responseText);
}
else
{
Console.WriteLine($"Error: {response.StatusCode}");
}
}
}
{
"statusCode": 200,
"message": "GET Request successful.",
"result": {
"totalRequests": 8,
"requestsByDate": [
{
"date": "2023-08-31",
"totalRequests": 8,
"requests": [
{
"endpoint": "/accounts/dji",
"count": 2
},
{
"endpoint": "/logs/{id}",
"count": 6
}
]
}
]
}
}
Retrieve API usage for a key
GET /v1/usage/{key}
Retrieves a list of billable API requests made using a single API key belonging to your API account.
Route Parameters
-
key string Required
Replace {key} in the route with the API key whose usage you want to retrieve.
URL Parameters
-
limit integer
Sets the maximum number of days to retrieve. The default value is 30.
Returns
Returns a JSON response with usage for the specified API key in result for the requested number of days. It includes totalRequests, counts, and daily totals and request counts in requestsByDate.
<html>
<body>
<div id="output">Retrieving API usage for a key...</div>
<script>
// Replace with your Flight Reader API secret key
const secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Replace with the API key whose usage you want to retrieve
const apiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Note: You should never expose your API secret key like this in a production environment.
// Anyone who has access to it will be able to make API requests on your behalf.
async function retrieveApiUsage() {
try {
// The optional "limit" parameter can be appended to the URL like this:
//
// https://api.flightreader.com/v1/usage/{key}?limit=7
const response = await fetch(`https://api.flightreader.com/v1/usage/${encodeURIComponent(apiKey)}`, {
headers: {
"Authorization": `Bearer ${secretApiKey}`
}
});
if (response.ok) {
const data = await response.json();
output.textContent = JSON.stringify(data.result);
} else {
output.textContent = `${response.status} ${response.statusText}`;
}
} catch (error) {
output.textContent = error;
}
}
retrieveApiUsage();
</script>
</body>
</html>
using System.IO.Compression;
using System.Text;
internal class Program
{
private static async Task Main()
{
// Replace with your Flight Reader API secret key
const string secretApiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// Replace with the API key whose usage you want to retrieve
const string apiKey = "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
using HttpClient client = new();
client.DefaultRequestHeaders.Add("Accept-Encoding", "gzip");
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {secretApiKey}");
// The optional "limit" parameter can be appended to the URL like this:
//
// https://api.flightreader.com/v1/usage/{key}?limit=7
var response = await client.GetAsync($"https://api.flightreader.com/v1/usage/{Uri.EscapeDataString(apiKey)}");
if (response.IsSuccessStatusCode)
{
var responseBytes = await response.Content.ReadAsByteArrayAsync();
if (response.Content.Headers.ContentEncoding.Contains("gzip"))
{
using var compressedStream = new MemoryStream(responseBytes);
await using var decompressionStream = new GZipStream(compressedStream, CompressionMode.Decompress);
using var decompressedStream = new MemoryStream();
await decompressionStream.CopyToAsync(decompressedStream);
responseBytes = decompressedStream.ToArray();
}
var responseText = Encoding.UTF8.GetString(responseBytes);
Console.WriteLine(responseText);
}
else
{
Console.WriteLine($"Error: {response.StatusCode}");
}
}
}
{
"statusCode": 200,
"message": "GET Request successful.",
"result": {
"totalRequests": 8,
"requestsByDate": [
{
"date": "2023-08-31",
"totalRequests": 8,
"requests": [
{
"endpoint": "/accounts/dji",
"count": 2
},
{
"endpoint": "/logs/{id}",
"count": 6
}
]
}
]
}
}
