Skip to content

Exports

Exports are asynchronous CSV generation of your shipments. You queue an export, poll it for completion, then download the finished file. Generation streams rows to disk on a worker, so large merchants never block a request. The export covers the same shipments you would see when listing shipments, constrained by the optional filters you pass.

Exports are scoped to your whole account, not to one carrier. A merchant with several licenses gets a single file covering all of them, so license_key is neither required nor accepted — the API ignores it if sent.

Lifecycle

StatusMeaning
pendingCreated; generation job queued.
processingThe worker is writing the CSV.
completedFile is ready. The export is emailed to you. download_url is null while the download route is disabled — see below.
failedGeneration errored; see error.

Only one export per license can be in flight at a time. Creating a second while one is still pending/processing returns 409 duplicate_request.


POST /exports

Queue an async shipments export (CSV build). Auth: client credentials. License: required.

Returns 202 Accepted — the file is not ready yet. Poll GET /exports/{export} until status is completed. The finished workbook is emailed to you; the /download route is currently disabled.

Parameters

Body

FieldTypeRequiredDescription
filterobjectnoFilter set constraining which shipments are exported (mirrors the shipments index). Any unsupplied filter is dropped.
filter.activebooleannoExport only active (true) or inactive (false) shipments. Omit to export both.

Example request

bash
curl --location 'https://app.shipos.co.il/api/v2/exports' \
--header 'X-Client-Id: {client_id}' \
--header 'X-Client-Secret: {client_secret}' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--data '{
  "license_key": "{license_key}",
  "filter": { "active": true }
}'
js
// Node.js 18+ / browsers — no dependencies
const response = await fetch('https://app.shipos.co.il/api/v2/exports', {
  method: 'POST',
  headers: {
    'X-Client-Id': process.env.SHIPOS_CLIENT_ID,
    'X-Client-Secret': process.env.SHIPOS_CLIENT_SECRET,
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    license_key: '{license_key}',
    filter: { active: true },
  }),
})

if (!response.ok) {
  const { error } = await response.json()
  throw new Error(`${error.code}: ${error.message}`)
}

const { data: exportJob } = await response.json() // 202 Accepted

console.log(exportJob.id, exportJob.status) // poll GET /exports/{id}
php
<?php
// composer require guzzlehttp/guzzle

$client = new \GuzzleHttp\Client([
    'base_uri' => 'https://app.shipos.co.il/api/v2/',
    'headers' => [
        'X-Client-Id' => getenv('SHIPOS_CLIENT_ID'),
        'X-Client-Secret' => getenv('SHIPOS_CLIENT_SECRET'),
        'Accept' => 'application/json',
    ],
]);

$response = $client->post('exports', [
    'json' => [
        'license_key' => '{license_key}',
        'filter' => ['active' => true],
    ],
]);

$export = json_decode($response->getBody()->getContents(), true)['data'];

echo $export['id'], ' ', $export['status'], PHP_EOL; // poll GET /exports/{id}
php
<?php

use Illuminate\Support\Facades\Http;

$export = Http::withHeaders([
    'X-Client-Id' => config('services.shipos.client_id'),
    'X-Client-Secret' => config('services.shipos.client_secret'),
])
    ->acceptJson()
    ->post('https://app.shipos.co.il/api/v2/exports', [
        'license_key' => '{license_key}',
        'filter' => ['active' => true],
    ])
    ->throw()
    ->json('data');

logger()->info("export {$export['id']} is {$export['status']}");
python
# pip install httpx
import os

import httpx

response = httpx.post(
    "https://app.shipos.co.il/api/v2/exports",
    headers={
        "X-Client-Id": os.environ["SHIPOS_CLIENT_ID"],
        "X-Client-Secret": os.environ["SHIPOS_CLIENT_SECRET"],
        "Accept": "application/json",
    },
    json={"license_key": "{license_key}", "filter": {"active": True}},
)
response.raise_for_status()
export = response.json()["data"]  # 202 Accepted

print(export["id"], export["status"])  # poll GET /exports/{id}
go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

func main() {
	body, _ := json.Marshal(map[string]any{
		"license_key": "{license_key}",
		"filter":      map[string]any{"active": true},
	})

	req, _ := http.NewRequest("POST", "https://app.shipos.co.il/api/v2/exports",
		bytes.NewReader(body))
	req.Header.Set("X-Client-Id", os.Getenv("SHIPOS_CLIENT_ID"))
	req.Header.Set("X-Client-Secret", os.Getenv("SHIPOS_CLIENT_SECRET"))
	req.Header.Set("Accept", "application/json")
	req.Header.Set("Content-Type", "application/json")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	var payload struct {
		Data struct {
			ID     int    `json:"id"`
			Status string `json:"status"`
		} `json:"data"`
	}
	if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
		panic(err)
	}

	fmt.Println(payload.Data.ID, payload.Data.Status) // poll GET /exports/{id}
}
java
// Java 17+ — java.net.http, no dependencies (parse with Jackson/Gson)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class ShipOsCreateExport {
    public static void main(String[] args) throws Exception {
        String body = """
            {"license_key":"{license_key}","filter":{"active":true}}
            """;

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create("https://app.shipos.co.il/api/v2/exports"))
            .header("X-Client-Id", System.getenv("SHIPOS_CLIENT_ID"))
            .header("X-Client-Secret", System.getenv("SHIPOS_CLIENT_SECRET"))
            .header("Accept", "application/json")
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(body))
            .build();

        HttpResponse<String> response = HttpClient.newHttpClient()
            .send(request, HttpResponse.BodyHandlers.ofString());

        if (response.statusCode() != 202) {
            throw new RuntimeException("ShipOS error: " + response.body());
        }

        System.out.println(response.body()); // {"data":{"status":"pending",...}}
    }
}
csharp
// .NET 8+ — System.Net.Http.Json
using System.Net.Http.Json;
using System.Text.Json;

using var http = new HttpClient
{
    BaseAddress = new Uri("https://app.shipos.co.il/api/v2/"),
};
http.DefaultRequestHeaders.Add("X-Client-Id",
    Environment.GetEnvironmentVariable("SHIPOS_CLIENT_ID"));
http.DefaultRequestHeaders.Add("X-Client-Secret",
    Environment.GetEnvironmentVariable("SHIPOS_CLIENT_SECRET"));

var response = await http.PostAsJsonAsync("exports", new
{
    license_key = "{license_key}",
    filter = new { active = true },
});
response.EnsureSuccessStatusCode(); // 202 Accepted

var payload = await response.Content.ReadFromJsonAsync<JsonDocument>()
    ?? throw new InvalidOperationException("Empty response");
var export = payload.RootElement.GetProperty("data");

Console.WriteLine($"{export.GetProperty("id").GetInt32()} " +
    $"{export.GetProperty("status").GetString()}");
ruby
require "net/http"
require "json"

uri = URI("https://app.shipos.co.il/api/v2/exports")
request = Net::HTTP::Post.new(uri)
request["X-Client-Id"] = ENV.fetch("SHIPOS_CLIENT_ID")
request["X-Client-Secret"] = ENV.fetch("SHIPOS_CLIENT_SECRET")
request["Accept"] = "application/json"
request["Content-Type"] = "application/json"
request.body = JSON.dump({ license_key: "{license_key}", filter: { active: true } })

response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
  http.request(request)
end

raise "ShipOS error: #{response.body}" unless response.is_a?(Net::HTTPSuccess)

export = JSON.parse(response.body).fetch("data") # 202 Accepted
puts "#{export["id"]} #{export["status"]}"
rust
// [dependencies]
// reqwest = { version = "0.12", features = ["json"] }
// tokio = { version = "1", features = ["full"] }
// serde_json = "1"
use serde_json::Value;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let payload: Value = reqwest::Client::new()
        .post("https://app.shipos.co.il/api/v2/exports")
        .header("X-Client-Id", std::env::var("SHIPOS_CLIENT_ID")?)
        .header("X-Client-Secret", std::env::var("SHIPOS_CLIENT_SECRET")?)
        .header("Accept", "application/json")
        .json(&serde_json::json!({
            "license_key": "{license_key}",
            "filter": { "active": true },
        }))
        .send()
        .await?
        .error_for_status()?
        .json()
        .await?;

    let export = &payload["data"];
    println!("{} {}", export["id"], export["status"]);
    Ok(())
}

Response 202

json
{
  "data": {
    "id": 812,
    "type": "shipments",
    "status": "pending",
    "platform": "api_v2",
    "row_count": null,
    "filename": null,
    "error": null,
    "download_url": null,
    "created_at": "2026-07-29T08:00:00.000000Z",
    "updated_at": "2026-07-29T08:00:00.000000Z"
  }
}

Errors

StatuscodeWhen
403forbiddenNo usable license (inactive/expired/not owned).
409duplicate_requestAn export for this license is already pending/processing (An export is already being prepared for this license.).
422validation_failedfilter is not an object, or filter.active is not a boolean.

GET /exports/

Return a single export owned by the caller's license — the polling endpoint. Auth: client credentials. License: required.

Parameters

Path

FieldTypeRequiredDescription
exportintegeryesThe export id returned by create.

Query

FieldTypeRequiredDescription

Example request

bash
curl --location 'https://app.shipos.co.il/api/v2/exports/812?license_key={license_key}' \
--header 'X-Client-Id: {client_id}' \
--header 'X-Client-Secret: {client_secret}' \
--header 'Accept: application/json'
js
// Node.js 18+ / browsers — no dependencies
const url = new URL('https://app.shipos.co.il/api/v2/exports/812')
url.searchParams.set('license_key', '{license_key}')

const response = await fetch(url, {
  headers: {
    'X-Client-Id': process.env.SHIPOS_CLIENT_ID,
    'X-Client-Secret': process.env.SHIPOS_CLIENT_SECRET,
    Accept: 'application/json',
  },
})

if (!response.ok) {
  const { error } = await response.json()
  throw new Error(`${error.code}: ${error.message}`)
}

const { data: exportJob } = await response.json()

// Poll until status === 'completed', then GET download_url.
console.log(exportJob.status, exportJob.row_count, exportJob.download_url)
php
<?php
// composer require guzzlehttp/guzzle

$client = new \GuzzleHttp\Client([
    'base_uri' => 'https://app.shipos.co.il/api/v2/',
    'headers' => [
        'X-Client-Id' => getenv('SHIPOS_CLIENT_ID'),
        'X-Client-Secret' => getenv('SHIPOS_CLIENT_SECRET'),
        'Accept' => 'application/json',
    ],
]);

$response = $client->get('exports/812', [
    'query' => ['license_key' => '{license_key}'],
]);

$export = json_decode($response->getBody()->getContents(), true)['data'];

// Poll until status is 'completed', then fetch download_url.
echo $export['status'], ' ', $export['download_url'] ?? '-', PHP_EOL;
php
<?php

use Illuminate\Support\Facades\Http;

$export = Http::withHeaders([
    'X-Client-Id' => config('services.shipos.client_id'),
    'X-Client-Secret' => config('services.shipos.client_secret'),
])
    ->acceptJson()
    ->get('https://app.shipos.co.il/api/v2/exports/812', [
        'license_key' => '{license_key}',
    ])
    ->throw()
    ->json('data');

// Poll until status is 'completed', then fetch download_url.
logger()->info($export['status'], ['download_url' => $export['download_url']]);
python
# pip install httpx
import os

import httpx

response = httpx.get(
    "https://app.shipos.co.il/api/v2/exports/812",
    params={"license_key": "{license_key}"},
    headers={
        "X-Client-Id": os.environ["SHIPOS_CLIENT_ID"],
        "X-Client-Secret": os.environ["SHIPOS_CLIENT_SECRET"],
        "Accept": "application/json",
    },
)
response.raise_for_status()
export = response.json()["data"]

# Poll until export["status"] == "completed", then GET download_url.
print(export["status"], export["row_count"], export["download_url"])
go
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

type exportResponse struct {
	Data struct {
		ID          int     `json:"id"`
		Status      string  `json:"status"`
		RowCount    *int    `json:"row_count"`
		DownloadURL *string `json:"download_url"`
	} `json:"data"`
}

func main() {
	req, _ := http.NewRequest("GET",
		"https://app.shipos.co.il/api/v2/exports/812?license_key={license_key}", nil)
	req.Header.Set("X-Client-Id", os.Getenv("SHIPOS_CLIENT_ID"))
	req.Header.Set("X-Client-Secret", os.Getenv("SHIPOS_CLIENT_SECRET"))
	req.Header.Set("Accept", "application/json")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	var export exportResponse
	if err := json.NewDecoder(res.Body).Decode(&export); err != nil {
		panic(err)
	}

	// Poll until Status == "completed", then GET DownloadURL.
	fmt.Println(export.Data.Status, export.Data.RowCount)
}
java
// Java 17+ — java.net.http, no dependencies (parse with Jackson/Gson)
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class ShipOsShowExport {
    public static void main(String[] args) throws Exception {
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(
                "https://app.shipos.co.il/api/v2/exports/812?license_key={license_key}"))
            .header("X-Client-Id", System.getenv("SHIPOS_CLIENT_ID"))
            .header("X-Client-Secret", System.getenv("SHIPOS_CLIENT_SECRET"))
            .header("Accept", "application/json")
            .build();

        HttpResponse<String> response = HttpClient.newHttpClient()
            .send(request, HttpResponse.BodyHandlers.ofString());

        if (response.statusCode() != 200) {
            throw new RuntimeException("ShipOS error: " + response.body());
        }

        // Poll until data.status is "completed", then GET data.download_url.
        System.out.println(response.body()); // {"data":{...}} — map with Jackson/Gson
    }
}
csharp
// .NET 8+ — System.Net.Http.Json
using System.Net.Http.Json;
using System.Text.Json;

using var http = new HttpClient
{
    BaseAddress = new Uri("https://app.shipos.co.il/api/v2/"),
};
http.DefaultRequestHeaders.Add("X-Client-Id",
    Environment.GetEnvironmentVariable("SHIPOS_CLIENT_ID"));
http.DefaultRequestHeaders.Add("X-Client-Secret",
    Environment.GetEnvironmentVariable("SHIPOS_CLIENT_SECRET"));

var payload = await http.GetFromJsonAsync<JsonDocument>(
    "exports/812?license_key={license_key}")
    ?? throw new InvalidOperationException("Empty response");
var export = payload.RootElement.GetProperty("data");

// Poll until status is "completed", then GET download_url.
Console.WriteLine($"{export.GetProperty("status").GetString()} " +
    $"{export.GetProperty("download_url")}");
ruby
require "net/http"
require "json"

uri = URI("https://app.shipos.co.il/api/v2/exports/812")
uri.query = URI.encode_www_form(license_key: "{license_key}")

request = Net::HTTP::Get.new(uri)
request["X-Client-Id"] = ENV.fetch("SHIPOS_CLIENT_ID")
request["X-Client-Secret"] = ENV.fetch("SHIPOS_CLIENT_SECRET")
request["Accept"] = "application/json"

response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
  http.request(request)
end

raise "ShipOS error: #{response.body}" unless response.is_a?(Net::HTTPSuccess)

export = JSON.parse(response.body).fetch("data")

# Poll until export["status"] == "completed", then GET download_url.
puts "#{export["status"]} #{export["row_count"]} #{export["download_url"]}"
rust
// [dependencies]
// reqwest = { version = "0.12", features = ["json"] }
// tokio = { version = "1", features = ["full"] }
// serde_json = "1"
use serde_json::Value;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let payload: Value = reqwest::Client::new()
        .get("https://app.shipos.co.il/api/v2/exports/812")
        .query(&[("license_key", "{license_key}")])
        .header("X-Client-Id", std::env::var("SHIPOS_CLIENT_ID")?)
        .header("X-Client-Secret", std::env::var("SHIPOS_CLIENT_SECRET")?)
        .header("Accept", "application/json")
        .send()
        .await?
        .error_for_status()?
        .json()
        .await?;

    let export = &payload["data"];
    // Poll until export["status"] == "completed", then GET download_url.
    println!("{} {}", export["status"], export["download_url"]);
    Ok(())
}

Response 200

json
{
  "data": {
    "id": 812,
    "type": "shipments",
    "status": "completed",
    "platform": "api_v2",
    "row_count": 1436,
    "filename": "shipments-20260729080000.csv",
    "error": null,
    "download_url": "https://app.shipos.co.il/api/v2/exports/812/download",
    "created_at": "2026-07-29T08:00:00.000000Z",
    "updated_at": "2026-07-29T08:00:12.000000Z"
  }
}

Response fields

FieldTypeDescription
idintegerExport id.
typestringExport discriminator; always shipments for this endpoint.
statusstringpending, processing, completed, or failed.
platformstringOrigin platform; always api_v2.
row_countinteger | nullNumber of data rows written. null until completed.
filenamestring | nullSuggested download filename. null until completed.
errorstring | nullFailure message when status is failed; null otherwise.
download_urlstring | nullAbsolute URL to /download. Populated only when status is completed; null otherwise.
created_at / updated_atstringISO-8601 timestamps.

Errors

StatuscodeWhen
403forbiddenNo usable license.
404not_foundNo export with that id belongs to the caller's license (Export not found.).
422validation_failedThe request body failed validation.

GET /exports/{export}/download

Not currently available

This route is disabled in the current release, so download_url comes back null even on a completed export and calling the path returns 404. Retrieve the file through the dashboard, or the email the export sends when it finishes. The reference below is kept for when the route is re-enabled.

Stream the completed export file as a CSV download. Auth: client credentials. License: required.

On success the response is not the JSON envelope — it is the raw file streamed with a Content-Disposition: attachment header (filename = the export's filename). The export must be completed and its file must exist on disk.

Parameters

Path

FieldTypeRequiredDescription
exportintegeryesThe export id.

Query

FieldTypeRequiredDescription

Example request

bash
curl --location 'https://app.shipos.co.il/api/v2/exports/812/download?license_key={license_key}' \
--header 'X-Client-Id: {client_id}' \
--header 'X-Client-Secret: {client_secret}' \
--header 'Accept: application/json' \
--output shipments.csv
js
// Node.js 18+ — the response is a file stream, not JSON
import { writeFile } from 'node:fs/promises'

const url = new URL('https://app.shipos.co.il/api/v2/exports/812/download')
url.searchParams.set('license_key', process.env.SHIPOS_LICENSE_KEY)

const response = await fetch(url, {
  headers: {
    'X-Client-Id': process.env.SHIPOS_CLIENT_ID,
    'X-Client-Secret': process.env.SHIPOS_CLIENT_SECRET,
  },
})

if (!response.ok) {
  const { error } = await response.json()
  throw new Error(`${error.code}: ${error.message}`)
}

await writeFile('shipments.csv', Buffer.from(await response.arrayBuffer()))
php
<?php
// composer require guzzlehttp/guzzle

$client = new \GuzzleHttp\Client([
    'headers' => [
        'X-Client-Id' => getenv('SHIPOS_CLIENT_ID'),
        'X-Client-Secret' => getenv('SHIPOS_CLIENT_SECRET'),
    ],
]);

// 'sink' streams straight to disk instead of buffering the body.
$client->get('https://app.shipos.co.il/api/v2/exports/812/download', [
    'query' => ['license_key' => getenv('SHIPOS_LICENSE_KEY')],
    'sink' => 'shipments.csv',
]);
php
<?php

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;

$response = Http::withHeaders([
    'X-Client-Id' => config('services.shipos.client_id'),
    'X-Client-Secret' => config('services.shipos.client_secret'),
])
    ->get('https://app.shipos.co.il/api/v2/exports/812/download', [
        'license_key' => config('services.shipos.license_key'),
    ])
    ->throw();

Storage::put('exports/shipments.csv', $response->body());
python
# pip install httpx
import os

import httpx

with httpx.stream(
    "GET",
    "https://app.shipos.co.il/api/v2/exports/812/download",
    params={"license_key": os.environ["SHIPOS_LICENSE_KEY"]},
    headers={
        "X-Client-Id": os.environ["SHIPOS_CLIENT_ID"],
        "X-Client-Secret": os.environ["SHIPOS_CLIENT_SECRET"],
    },
) as response:
    response.raise_for_status()
    with open("shipments.csv", "wb") as file:
        for chunk in response.iter_bytes():
            file.write(chunk)
go
package main

import (
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
)

func main() {
	query := url.Values{"license_key": {os.Getenv("SHIPOS_LICENSE_KEY")}}
	endpoint := "https://app.shipos.co.il/api/v2/exports/812/download?" + query.Encode()

	req, _ := http.NewRequest("GET", endpoint, nil)
	req.Header.Set("X-Client-Id", os.Getenv("SHIPOS_CLIENT_ID"))
	req.Header.Set("X-Client-Secret", os.Getenv("SHIPOS_CLIENT_SECRET"))

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	if res.StatusCode != http.StatusOK {
		panic(fmt.Sprintf("ShipOS error: HTTP %d", res.StatusCode))
	}

	file, err := os.Create("shipments.csv")
	if err != nil {
		panic(err)
	}
	defer file.Close()

	io.Copy(file, res.Body)
}
java
// Java 17+ — BodyHandlers.ofFile writes the stream straight to disk
import java.net.URI;
import java.nio.file.Path;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class ShipOsExportDownload {
    public static void main(String[] args) throws Exception {
        String url = "https://app.shipos.co.il/api/v2/exports/812/download"
            + "?license_key=" + System.getenv("SHIPOS_LICENSE_KEY");

        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(url))
            .header("X-Client-Id", System.getenv("SHIPOS_CLIENT_ID"))
            .header("X-Client-Secret", System.getenv("SHIPOS_CLIENT_SECRET"))
            .build();

        HttpResponse<Path> response = HttpClient.newHttpClient()
            .send(request, HttpResponse.BodyHandlers.ofFile(Path.of("shipments.csv")));

        if (response.statusCode() != 200) {
            throw new RuntimeException("ShipOS error: HTTP " + response.statusCode());
        }
    }
}
csharp
// .NET 8+ — copy the response stream to a file
using var http = new HttpClient();
http.DefaultRequestHeaders.Add("X-Client-Id",
    Environment.GetEnvironmentVariable("SHIPOS_CLIENT_ID"));
http.DefaultRequestHeaders.Add("X-Client-Secret",
    Environment.GetEnvironmentVariable("SHIPOS_CLIENT_SECRET"));

var licenseKey = Environment.GetEnvironmentVariable("SHIPOS_LICENSE_KEY");
using var response = await http.GetAsync(
    $"https://app.shipos.co.il/api/v2/exports/812/download?license_key={licenseKey}",
    HttpCompletionOption.ResponseHeadersRead);

response.EnsureSuccessStatusCode();

await using var file = File.Create("shipments.csv");
await response.Content.CopyToAsync(file);
ruby
require "net/http"
require "uri"

uri = URI("https://app.shipos.co.il/api/v2/exports/812/download")
uri.query = URI.encode_www_form(license_key: ENV.fetch("SHIPOS_LICENSE_KEY"))

request = Net::HTTP::Get.new(uri)
request["X-Client-Id"] = ENV.fetch("SHIPOS_CLIENT_ID")
request["X-Client-Secret"] = ENV.fetch("SHIPOS_CLIENT_SECRET")

Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
  http.request(request) do |response|
    raise "ShipOS error: #{response.code}" unless response.is_a?(Net::HTTPSuccess)

    File.open("shipments.csv", "wb") do |file|
      response.read_body { |chunk| file.write(chunk) }
    end
  end
end
rust
// [dependencies]
// reqwest = { version = "0.12" }
// tokio = { version = "1", features = ["full"] }
use std::fs::File;
use std::io::Write;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let bytes = reqwest::Client::new()
        .get("https://app.shipos.co.il/api/v2/exports/812/download")
        .query(&[("license_key", std::env::var("SHIPOS_LICENSE_KEY")?)])
        .header("X-Client-Id", std::env::var("SHIPOS_CLIENT_ID")?)
        .header("X-Client-Secret", std::env::var("SHIPOS_CLIENT_SECRET")?)
        .send()
        .await?
        .error_for_status()?
        .bytes()
        .await?;

    File::create("shipments.csv")?.write_all(&bytes)?;
    Ok(())
}

Response 200

Streamed CSV file (text/csv) sent as an attachment. There is no JSON body.

Errors

StatuscodeWhen
403forbiddenNo usable license.
404not_foundExport not found for this license (Export not found.), or the export is not yet completed / its file is missing (Export file is not ready.). Poll GET /exports/{export} until status is completed before downloading.
422validation_failedThe request body failed validation.