אימות
ה-ShipOS v2 API מאמת קוראים באמצעות פרטי לקוח API — X-Client-Id ו-X-Client-Secret — הנשלחים ככותרות HTTP בכל בקשה ל-endpoint מאומת. רק GET /ping ציבורי; כל שאר ה-endpoints, כולל מעקב, דורשים פרטי גישה.
איך זה עובד
שלחו את שתי כותרות פרטי הגישה, בתוספת Accept: application/json, בכל בקשה מאומתת:
| Header | חובה | תיאור |
|---|---|---|
X-Client-Id | כן | מזהה הלקוח הציבורי של החשבון. |
X-Client-Secret | כן | הסוד (secret) של החשבון. מושווה בצד השרת בזמן קבוע (hash_equals). |
Accept | מומלץ | שלחו תמיד application/json. |
ה-X-Client-Id מזהה את חשבון הסוחר (User). החשבון מוצמד לבקשה כדי שה-endpoint יפעל תחתיו. אין החלפת token ואין session — כל בקשה נושאת את פרטי הגישה ישירות.
מאיפה מגיעים פרטי הגישה
פרטי הגישה הם לכל חשבון סוחר. הם נוצרים ומחודשים על ידי מנהל מערכת של ShipOS מעמוד עריכת הלקוח בפאנל הניהול של ShipOS. אין endpoint ב-v2 API ליצירה, החלפה או שליפה של פרטי גישה — זו פעולה שמתבצעת בפאנל הניהול בלבד.
מקרי כישלון
שתי השגיאות משתמשות בקוד unauthenticated וב-HTTP 401, עטופות במעטפת השגיאה הסטנדרטית:
| תנאי | Status | code | message |
|---|---|---|---|
| אחת הכותרות חסרה | 401 | unauthenticated | Missing API credentials. Send X-Client-Id and X-Client-Secret. |
X-Client-Id לא מוכר, או X-Client-Secret שגוי | 401 | unauthenticated | Invalid API client credentials. |
{
"error": {
"code": "unauthenticated",
"message": "Invalid API client credentials.",
"status": 401
}
}דוגמה מלאה
שליפת החשבון הקורא עם GET /account:
curl --location 'https://app.shipos.co.il/api/v2/account' \
--header 'X-Client-Id: {client_id}' \
--header 'X-Client-Secret: {client_secret}' \
--header 'Accept: application/json'// Node.js 18+ / דפדפנים — בלי תלויות
const response = await fetch('https://app.shipos.co.il/api/v2/account', {
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}`) // unauthenticated ב-401
}
const { data: account } = await response.json()
console.log('Authenticated as', account.company_name)<?php
// composer require guzzlehttp/guzzle
$client = new \GuzzleHttp\Client();
$response = $client->get('https://app.shipos.co.il/api/v2/account', [
'headers' => [
'X-Client-Id' => getenv('SHIPOS_CLIENT_ID'),
'X-Client-Secret' => getenv('SHIPOS_CLIENT_SECRET'),
'Accept' => 'application/json',
],
]);
$account = json_decode($response->getBody()->getContents(), true)['data'];
echo 'Authenticated as ', $account['company_name'], PHP_EOL;<?php
use Illuminate\Support\Facades\Http;
$account = 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/account')
->throw()
->json('data');
logger()->info('Authenticated as '.$account['company_name']);# pip install httpx
import os
import httpx
response = httpx.get(
"https://app.shipos.co.il/api/v2/account",
headers={
"X-Client-Id": os.environ["SHIPOS_CLIENT_ID"],
"X-Client-Secret": os.environ["SHIPOS_CLIENT_SECRET"],
"Accept": "application/json",
},
)
response.raise_for_status() # זורק שגיאה ב-401 unauthenticated
print("Authenticated as", response.json()["data"]["company_name"])package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("GET", "https://app.shipos.co.il/api/v2/account", 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()
if res.StatusCode != http.StatusOK {
panic(fmt.Sprintf("ShipOS error: HTTP %d", res.StatusCode))
}
var payload struct {
Data struct {
CompanyName string `json:"company_name"`
} `json:"data"`
}
if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
panic(err)
}
fmt.Println("Authenticated as", payload.Data.CompanyName)
}// Java 17+ — java.net.http, בלי תלויות
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class ShipOsCredentialCheck {
public static void main(String[] args) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://app.shipos.co.il/api/v2/account"))
.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());
}
System.out.println(response.body()); // {"data":{...}}
}
}// .NET 8+ — System.Net.Http.Json
using System.Net.Http.Json;
using System.Text.Json;
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"));
http.DefaultRequestHeaders.Add("Accept", "application/json");
var payload = await http.GetFromJsonAsync<JsonDocument>(
"https://app.shipos.co.il/api/v2/account")
?? throw new InvalidOperationException("Empty response");
var account = payload.RootElement.GetProperty("data");
Console.WriteLine($"Authenticated as {account.GetProperty("company_name").GetString()}");require "net/http"
require "json"
uri = URI("https://app.shipos.co.il/api/v2/account")
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)
account = JSON.parse(response.body).fetch("data")
puts "Authenticated as #{account["company_name"]}"// [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/account")
.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?;
println!("Authenticated as {}", payload["data"]["company_name"]);
Ok(())
}קריאה מוצלחת מחזירה את חשבון הסוחר עטוף ב-{ "data": { ... } }. ראו GET /account למבנה התגובה המלא.