API Integration Guide
How to sign a request, how replies and errors behave, and how to embed the 2D, 3D and dashboard views in your own pages.
This guide explains how to call the BAPLIE Viewer Online API and how to show the 2D view, the 3D view and the dashboard inside your own web pages.
The complete list of calls, their fields and example replies is in the API reference. This guide covers what the reference does not: how to sign a request, how replies and errors behave, and how to embed the viewers.
1. What you need
| Item | Where it comes from |
|---|---|
| API key | Your facility administrator finds it in BAPLIE Viewer Online under Manage Facility → Developers. It is sent in every request and is not secret. |
| Private key | Same place. It is used to sign requests and must stay secret: keep it on your server, never put it in a web page, mobile app or any code a user can open. |
| Base URL | https://api-gateway.baplieviewer.com/v4 |
API access has to be switched on for your facility by TEDIVO first. If you do not see the Developers tab, ask TEDIVO to enable it. API access is a paid feature available on enterprise plans only; contact us to discuss pricing.
2. Making a request
Every call is an HTTP POST to BASE_URL/<call>, for example
https://api-gateway.baplieviewer.com/v4/read/json.
- Header
x-api-key: <your API key>. - Body: ordinary form fields (
application/x-www-form-urlencoded). JSON bodies are not accepted. - Every call except
health/statusincludes ahashfield (section 3). - Call the API from your server. A browser cannot call it directly, because it cannot send the
x-api-keyheader safely and the private key must not be in the browser.
Example, reading the file you uploaded as MyVessel001:
curl -X POST https://api-gateway.baplieviewer.com/v4/read/json \
-H "x-api-key: YOUR_API_KEY" \
--data-urlencode "file_id=MyVessel001" \
--data-urlencode "hash=0fe0707288b2e23628acacd9879542f1ae408142e41ed8b4693b0f33b42e363fd90c93c40db4e1b74d15163dff3eeaefb42e8b9aecb46d93f26a29b93abd914d"
The hash above was made with the example private key EXAMPLE_PRIVATE_KEY; yours
will differ.
A quick test that your key works, no hash needed:
curl -X POST https://api-gateway.baplieviewer.com/v4/health/status -H "x-api-key: YOUR_API_KEY"
Expected reply: {"ack":"success","error_code":0}
3. The request hash — read this first
The hash proves that a request really comes from you and was not changed on the way. It is a SHA-512 of some of the request's own field values followed by your private key. The server repeats the calculation with the private key it has stored for you; if the two results differ the request is refused.
3.1 How to build it
- Look up the call in the table in section 9. It lists which fields go into the hash, and in what order.
- Take the values of those fields, exactly as you send them in the request, and join them one after another with nothing in between — no spaces, commas or line breaks of your own.
- Add your private key to the end.
- Calculate SHA-512 of that text (UTF-8) and write the result as lowercase hexadecimal, 128 characters.
- Send it as the
hashfield.
In other words: hash = sha512( value1 + value2 + ... + privateKey )
3.2 Worked examples
Private key used here: EXAMPLE_PRIVATE_KEY — use your own.
| Call | Fields in the hash, in order | Text that is hashed | Hash |
|---|---|---|---|
read/json | file_id | MyVessel001EXAMPLE_PRIVATE_KEY | 0fe0707288b2e23628acacd9879542f1ae408142e41ed8b4693b0f33b42e363fd90c93c40db4e1b74d15163dff3eeaefb42e8b9aecb46d93f26a29b93abd914d |
delete/file | file_id, file_upload_id | MyVessel0011234EXAMPLE_PRIVATE_KEY | 5e3e34e783c8380f354c9a32d129f0cf536a76cac3352983cc4b3ed84d9453a712e2e4f6b05c9c1a328ac407490859f2c9fc4fae54dddae87bbb9b2eaf8da334 |
view/2D | file_id, user_id | MyVessel001user-1EXAMPLE_PRIVATE_KEY | 9c1308a9b159c75fc61fab504ecb8641ff11cb7a3ac4a75f34229cf6ee96460fb7f9b6daf5abc7b0189aa53a9d08c8a53b141af4b0287de69ab3f78f8abf3edb |
upload | data, file_id, main_json, anomalies_json | EDI-DATAMyVessel00100EXAMPLE_PRIVATE_KEYwith data=EDI-DATA, main_json=0, anomalies_json=0 | 456ac42de934071d8e1847bb5bddf32e828cd040ebb70fe98b65979d7ea7e9670f9f6bfcf698c4252bb085526d9bcfec80e7bdb0d00dd9e09a19f981d6d19389 |
You can check your own code against the first row: with file_id=MyVessel001 and
private key EXAMPLE_PRIVATE_KEY you must get the hash that starts
0fe07072. On the command line:
printf '%s' 'MyVessel001EXAMPLE_PRIVATE_KEY' | openssl dgst -sha512
3.3 The same calculation in each language
Python
import hashlib
def make_hash(*values):
return hashlib.sha512(("".join(values) + PRIVATE_KEY).encode("utf-8")).hexdigest()
make_hash("MyVessel001")
PHP
function make_hash($privateKey, ...$values) {
return hash('sha512', implode('', $values) . $privateKey);
}
make_hash($privateKey, 'MyVessel001');
Node.js
import { createHash } from 'node:crypto';
const makeHash = (...values) =>
createHash('sha512').update(values.join('') + PRIVATE_KEY, 'utf8').digest('hex');
makeHash('MyVessel001');
Java
static String makeHash(String... values) throws Exception {
byte[] digest = MessageDigest.getInstance("SHA-512")
.digest((String.join("", values) + PRIVATE_KEY).getBytes(StandardCharsets.UTF_8));
StringBuilder hex = new StringBuilder();
for (byte b : digest) hex.append(String.format("%02x", b));
return hex.toString();
}
Shell
make_hash() {
printf '%s' "$(printf '%s' "$@")${PRIVATE_KEY}" | openssl dgst -sha512 -r | cut -d' ' -f1
}
Any language works: it is a plain SHA-512 of a text. Complete runnable clients are in section 8.
3.4 Things that go wrong, and how to spot them
- The values in the hash must be the values you send. If you hash
file_idbut send it with different spelling, spaces or case, the hash fails. - Hash the raw value, not the URL-encoded one. Hash the text of
dataorjson_dataas it is in your program; your HTTP library encodes it for the form on its own. If you encode it yourself and let the library encode it again, the server sees a different text. - Line endings in
datacount. The text of an uploaded EDI file is hashed character for character, including its line breaks. Read the file once, and use that same text for the hash and for thedatafield. - Order matters.
file_idthenfile_upload_idis not the same asfile_upload_idthenfile_id. Follow the table in section 9 — note thatread/urlhashesfile_upload_idfirst. - Fields that are not in the table are not hashed. For example
api_file_typeandreport_typeare sent but are not part of the hash. - Use the id you sent, not the id the server stores. The server keeps your file under an internal name, but in every request, and in every hash, you use the
file_idyou chose when you uploaded the file. - Lowercase hex. Uppercase hexadecimal is not accepted.
- No private key in the request. Only the hash is sent; the private key itself never travels.
- Hash mismatch error codes differ per call — for example 202 for
upload, 302 forread/*, 504 forview/2D, 900 foraccount/*. They all mean the hash the server calculated differs from yours.
4. Replies and errors
Replies are JSON with at least ack and error_code:
{"ack":"success","error_code":0}
{"ack":"failed","error_code":302,"error_msg":"API not from trusted source","error_details":"HASH does not match expected value"}
200 even when ack is failed.error_msgis a fixed text for the code.error_details, present only when there is something to add, gives more — for example the list of fields that were missing.- Error codes belong to a call. The same number can mean different things in different calls, so read an error code together with the call that returned it. All codes and examples per call are in the API reference.
- The 2D, 3D and dashboard calls do not answer with JSON when they succeed; see section 6.
Problems found by the gateway itself, before the API sees the request, look
different and use HTTP status 403:
{"ack":"failed","error_code":9994,"error_msg":"Forbidden","error_detail":"Forbidden"}
the x-api-key header is missing or wrong
{"ack":"failed","error_code":9995,"error_msg":"Missing Authentication Token","error_detail":"Missing Authentication Token"}
the call name or method does not exist - check the spelling and use POST
Note error_detail without the final s in these two, unlike the API's own
error_details.
Most common codes
| Code | Meaning |
|---|---|
102 | The x-api-key header was not sent (reaches the API only on direct calls) |
105 | The request was refused as not coming from an authorised source |
103 / 104 / 106 | Facility membership expired / no permission for the file type / your facility is not allowed to use this call |
2xx, 3xx … 18xx | Call specific, see the reference. The “required parameters missing or empty” and “API not from trusted source” errors exist in every call. |
5. A typical workflow
- Upload a BAPLIE file with
upload. You choose thefile_id— letters, digits,-and_only. The reply containsfile_upload_id: store it, several later calls need it. - Read it back as JSON (
read/json), get its anomalies (read/anomalies), a cargo summary (read/cargoSummary), a MOVINS for a port (read/movins) or one container (read/container). - Change it with the
container/*andoperations/*calls. These only work on files uploaded through the API. - Show it to your users with the 2D view, 3D view or dashboard (section 6).
- Delete it with
delete/filewhen you no longer need it.
account/checkAccess returns the list of calls your facility may use.
account/status shows the state of your membership. health/status
needs no hash and is meant for monitoring.
Which calls change files: all container/* and
operations/* calls change the stored file. When the facility has file backups
switched on, the first change keeps an _original copy of the file as it was
before.
5.1 CSV uploads: header details and default values
upload/csvFile turns a CSV file into a BAPLIE. The CSV holds the containers; the
BAPLIE also needs header details about the vessel call — sender, recipient, vessel, voyage,
ports and dates. For each of them you can either send the value in the call, or save a default
once in BAPLIE Viewer Online. A value that you send in the call always wins;
when the call does not send it, or sends it empty, the saved default is used.
Where to set the defaults: Manage Facility → Developers → Advanced options: CSV upload defaults (facility administrators). The section is hidden until you press the button, and is shown from the start when a value has been saved. Fill in what stays the same for every file; leave a field empty if you always send it. A field you leave empty stays empty in the BAPLIE (ports and dates are then left out altogether).
| Field in the call | Default in the Developers tab | What it is | Format |
|---|---|---|---|
sender_id | Sender ID | Who sent the BAPLIE (header: sender identification) | text |
rec_id | Recipient ID | Who the BAPLIE is for (header: recipient identification) | text |
vessel_nbr | Voyage Number | Voyage number of the vessel call | text |
vessel_car_id | Vessel carrier | Carrier (shipping line) code of the vessel | text |
vessel_ref_id | Vessel Reference | Vessel reference, for example the Lloyd's or IMO number | text |
vessel_name | Vessel Name | Name of the vessel | text |
place_depart | Place of Departure | Port the vessel left | UN/LOCODE, for example USLAX |
next_port | NPOC | Next port of call | UN/LOCODE, for example USNYC |
act_date | ATA | Actual time of arrival | YYMMDDHHMM, for example 2610211530 |
est_date | ETA | Estimated time of arrival | YYMMDD, for example 261021 |
act_date_dep | ATD | Actual time of departure | YYMMDDHHMM, for example 2610220800 |
est_date_dep | ETD | Estimated time of departure | YYMMDD, for example 261022 |
port_load_override_h | POL Override | Port of load to use for every container instead of the port in the CSV file | UN/LOCODE |
Good to know
- The defaults are used by the CSV upload call only. No other API call reads them.
port_load_override_hworks only when you send it with each call. The value saved under POL Override is not applied yet.- The first twelve rows follow the rule above (call value first, saved default second). If neither exists, the field stays empty in the BAPLIE; ports and dates are then left out altogether.
6. Showing the 2D view, 3D view and dashboard in your page
The three views are shown by BAPLIE Viewer Online itself, inside an <iframe>
in your page. You do not draw anything yourself.
6.1 How it works
- Your page contains an iframe whose
srcis a script on your server. - That script calls the API (
view/2D,view/3Dorview/dashboard) with your keys and a hash. - The API answers with a short HTML page, not JSON, containing a form which submits itself.
- Your script sends that HTML to the browser unchanged.
- The browser submits the form inside the iframe and the viewer opens, served from
https://app.baplieviewer.com.
The browser never sees your private key. It does receive your API key, inside the HTML page; that key is not secret.
6.2 The three calls
| View | Call | Fields | Hash |
|---|---|---|---|
| 2D view | view/2D | file_id, user_id (optional), bay (optional), hash | file_id + user_id + private keywithout user_id: file_id + private key |
| 3D view | view/3D | file_id, user_id (optional), bay (optional), hash | file_id + user_id + private keywithout user_id: file_id + private key |
| Dashboard | view/dashboard | file_id, hash | file_id + private key |
file_id is the id you chose at upload. user_id identifies the person
using your application (letters, digits and -). It is used with
view/restrictContainers to limit which containers a person can see: call
view/restrictContainers once per person and file with the container numbers they
may see (all_bays=1 lets them see everything) and give the same
user_id here. If you get an error saying no container list exists for the user
(code 502 for 2D, 602 for 3D), create the list first. bay opens the view on that
bay.
6.3 The page and the script
Your page:
<iframe src="/embed.php?view=2d&file=MyVessel001"
style="width:100%;height:80vh;border:0" allow="fullscreen"></iframe>
The script on your server, PHP shown:
<?php
$views = ['2d' => 'view/2D', '3d' => 'view/3D', 'dashboard' => 'view/dashboard'];
$view = $_GET['view'] ?? '';
$fileId = $_GET['file'] ?? '';
if (!isset($views[$view]) || !preg_match('/^[A-Za-z0-9_-]+$/', $fileId)) { http_response_code(400); exit; }
// YOUR check: may the signed-in user of your application see this file?
$userId = 'user-1'; // the person using your application
$hash = ($view === 'dashboard')
? hash('sha512', $fileId . $privateKey)
: hash('sha512', $fileId . $userId . $privateKey);
$fields = ($view === 'dashboard') ? ['file_id' => $fileId, 'hash' => $hash]
: ['file_id' => $fileId, 'user_id' => $userId, 'hash' => $hash];
$ch = curl_init('https://api-gateway.baplieviewer.com/v4/' . $views[$view]);
curl_setopt_array($ch, [CURLOPT_POST => true, CURLOPT_POSTFIELDS => http_build_query($fields),
CURLOPT_HTTPHEADER => ['x-api-key: ' . $apiKey], CURLOPT_RETURNTRANSFER => true]);
$answer = curl_exec($ch);
if (strpos(ltrim($answer), '{') === 0) { http_response_code(502); exit($answer); } // a JSON answer is an error
header('Content-Type: text/html; charset=utf-8');
echo $answer;
6.4 Rules to follow
- Check access yourself. The script is the only thing standing between a user and any file of your facility. Make sure the person asking for
file=...is allowed to see that file, and never take afile_idfrom the browser without checking it. - Use HTTPS on your page.
- Your page's own
Content-Security-Policy, if you have one, must allow framinghttps://app.baplieviewer.com(frame-src https://app.baplieviewer.com). - Give the iframe room. The viewers are designed for a window of at least about 1000 by 600 pixels.
- Pass the HTML through unchanged. Do not rebuild or re-sign the form inside it; its hidden fields are part of the API and can change between releases. The sample scripts simply forward whatever the view call answers.
- Do not link to the viewer directly, and do not put the viewer address in your own pages. Always go through your script and the API call.
- The first time a vessel is opened, a prompt about an Open Vessel Definition file can appear inside the 2D view. Choose “Don't use OVD file” to continue.
7. Going live: what TEDIVO has to set up
The viewers only appear inside your page if TEDIVO has allowed your website. Browsers refuse to show a page inside an iframe unless the page being shown says that your site may frame it. Until TEDIVO has added your website, the iframe stays empty — a grey box with a sad face icon in most browsers — even though the API call itself works.
Please send TEDIVO, before the first test:
- The exact addresses of the pages that will contain the iframes, with
https://and the host name, for examplehttps://portal.example.com. If you have several — a test site, a production site — list them all. If every sub-domain needs access, say so (https://*.example.com). - The kind of browsers your users use. Some browsers block the cookies the viewer needs when it runs inside another site's page; TEDIVO can check this with you.
TEDIVO adds your addresses to the allow list on all BAPLIE Viewer Online web servers (the
frame-ancestors setting). Allow some time for this; it is a configuration change
on each server. Get in touch to start.
This is separate from the API calls: the API calls are made from your server and do not depend on this list. A 403 with code 9994 or 9995 (section 4) is not caused by it.
8. Code samples
Complete, runnable clients that upload a file, read it back, fetch the 2D view page and delete the file again. They use only the standard library of each language. Set the environment variables first:
export BVO_API_KEY=your-api-key
export BVO_PRIVATE_KEY=your-private-key
| Language | File | Run |
|---|---|---|
| Python 3 | bvo_api.py | python3 bvo_api.py my_baplie.edi |
| PHP 7.4+ (curl) | bvo_api.php | php bvo_api.php my_baplie.edi |
| Node.js 18+ | bvo_api.mjs | node bvo_api.mjs my_baplie.edi |
| Java 11+ | BvoApi.java | java BvoApi.java my_baplie.edi |
| Shell (curl, openssl) | bvo_api.sh | bash bvo_api.sh my_baplie.edi |
| Embedded viewer (PHP) | embed.php + page.html | php -S 127.0.0.1:8080 |
| Embedded viewer (Node.js) | embed.mjs | node embed.mjs |
Every sample takes the base address from BVO_BASE_URL, defaulting to
https://api-gateway.baplieviewer.com/v4.
upload, read/json, view/2D and delete/file only. Each one uploads a file, reads it back, fetches the 2D view page and deletes the file again, so run them against a file you are happy to have removed.9. Quick reference: hash recipes
Fields are listed in the order they go into the hash. The private key is always added at the very end. A dash means the call has no hash.
| Call | Hash = sha512 of |
|---|---|
health/status | — no hash, no fields |
upload, upload/loadList, upload/csvFile | data + file_id + main_json + anomalies_json |
read/json, read/anomalies, read/cargoSummary, read/baplie, read/movins | file_id |
read/url | file_upload_id + file_id |
read/container, container/deleteContainer, operations/dischargeContainers | file_id + file_upload_id + container_id |
container/createContainer, container/updateContainer, operations/loadContainers, operations/restowContainers, operations/correctPositions | file_id + file_upload_id + json_data |
delete/file | file_id + file_upload_id |
view/2D, view/3D | file_id + user_idwithout user_id: file_id |
view/dashboard | file_id |
view/restrictContainers | file_id + user_id + container_ids + all_bays |
share/listGuests, account/status, account/checkAccess | date |
account/usage | start_date + end_date |
share/assignGuestFile | guest_user_id + file_id |
activity/322 | voyage_number + vessel_name + data |
activity/recordEvent | event_type + voyage_number + vessel_name + container_number + equipment_status + weight + weight_qualifier + iso_code + carrier_line + temperature |
If a hash is rejected and you cannot see why, check section 3.4 first — encoding and field order account for almost every case.