SFTP File Upload
The no-code path into Klappir. Drop a UTF-8 CSV onto your SFTP folder — Klappir processes uploaded files within 24 hours of file upload — no integration code, no engineering required. Access is SSH key only: you send Klappir your public key, we provision the folder, you connect with your private key.
Acme Logistics — a freight and waste-haulage company in Reykjavík with registration number 5501234567. Acme exports diesel deliveries, runs for upstream transportation & distribution, and waste pickups from their Business Central ERP every night, then drops the resulting CSVs onto their Klappir SFTP folder.
In customer scenarios, Acme uploads its own diesel and waste data.In partner scenarios, Acme is a fuel and waste supplier sending data on behalf of three downstream Klappir customers.
SSH key setup, connection test, and your first CSV upload.
Two stages: connect with your SSH key, then prepare and upload CSVs.
Encoding, separators, headers, and the conventions every template shares.
Re-upload by Unique ID; zero quantity removes a row.
24-hour processing SLA and how to confirm ingestion.
Scheduling exports, handling failures, and monitoring uploads.
SFTP is the right choice when your data already lives in spreadsheets, ERP exports (like Business Central), or accounting software. You don't need to write code or maintain a service. The trade-off: it's batch, not real-time — uploaded files are processed within 24 hours of file upload.
If you need real-time submission, programmatic batching, or fine-grained error handling, see the GraphQL API instead.
Getting started #
Once Klappir has installed your SSH public key, you can usually connect and upload within about one business day. Use the checklist for your audience below.
- 1Email service@klappir.com and request SFTP access.Include your company registration number and the SSH public key Klappir should install — paste the full single-line
.pubcontents (OpenSSH format, e.g. starts withssh-ed25519orssh-rsa). How to generate a key pair is explained in the Get your credentials section. Never send your private key. - 2Connect to your SFTP folderKlappir replies with your hostname, username, and path. Access is SSH key only (no password). In FileZilla, Cyberduck, WinSCP, or with
sftp -i /path/to/private_key …, authenticate using the private key that matches the public key you submitted. - 3Look up UNSPSC codesEach row needs at least one classification — see Classifications and the UNGM UNSPSC browser.4Pick and fill the right templateSeven CSV templates cover fuel, upstream transportation & distribution, waste, three flavours of business travel, and goods & services. Download the Excel file from the template page you need, fill your rows, and save as UTF-8 CSV.5Upload to your top-level folderPut the finished file in the home directory Klappir assigns you (not a subfolder unless Klappir told you otherwise). Klappir processes each file within 24 hours of upload.Your path as a data partner
- 1Request partner SFTP accessEmail service@klappir.com with your organisation details and the SSH public key to install (same
.pubrules as for customers). Klappir provisions one folder per downstream customer, keyed by their registration number; one login reaches all folders you are authorised for. - 2Use the standard templatesSame seven templates as customers. Every row must carry the correct
Legal Entity (UUID)for the customer that row belongs to. - 3Upload into each customer's directorySave UTF-8 CSVs and place them in the folder Klappir assigns for that customer under your partner root. Exact paths are confirmed when each customer is onboarded.
Connection details are issued per customerHostnames and example usernames in this guide are placeholders. The real values for your account are sent to you by email when access is provisioned.
Your journey, in two stages #
Every SFTP integration moves through the same two stages. The sidebar groups operations by stage so you always know where you are.
Stage 1 — Connect
Klappir provisions an SFTP folder for you, keyed by your registration number. You send your SSH public key when requesting access; Klappir installs it and emails you the hostname, username, and path. You always authenticate with the matching private key — passwords are not used.
Stage 2 — Prepare and upload
Pick the CSV template that matches the data you have. Seven templates cover everything Klappir currently accepts — fuel, upstream transportation & distribution, waste, three kinds of business travel, and goods & services. Fill in the columns, save as UTF-8 CSV, and upload the file to your top-level SFTP folder.
Partner — multiple customers, one set of credentialsAs a partner, you get one SFTP login that has access to multiple customer folders — one per customer who has authorized your integration. Each folder is keyed by the customer's registration number. New customers appear as new folders within a day of authorizing your integration.
Stage 3 — After upload
After upload, allow up to 24 hours for processing. Then view and validate data in the Klappir platform, use edit / update / delete when corrections are needed, and consult common errors if rows are missing or rejected.
Customer — one folder, your own dataAs a direct customer, you have a single folder keyed by your own registration number. Every file you drop is attributed to your organisation automatically.
CSV format basics #
All seven templates share the same conventions. Get these right once and they apply everywhere.
Convention Detail File format Comma-separated CSV ( .csv). Excel files (.xlsx) are accepted but CSV is preferred — saves a conversion step.Encoding UTF-8. Critical for non-ASCII characters in addresses, labels, and references (e.g. Icelandic names like Reykjavík). Separator Comma ( ,). Use double quotes around values that contain commas, e.g."Borgartún 21, 105 Reykjavík".Header row First row must be the column headers exactly as shown in the template. Don't translate them or change the order. Required fields Columns marked with *in the template header are required. Klappir rejects rows missing required values.Dates ISO 8601 ( YYYY-MM-DD) preferred. Excel-style dates (DD/MM/YYYY) are accepted and auto-detected by locale.Decimals Use .as the decimal separator (123.45), not comma.Country codes ISO 3166-1 alpha-2, e.g. ISfor Iceland,DKfor Denmark.Filename Use a descriptive name including the date range, e.g. fuel_2024-03.csv. Filenames don't affect routing — Klappir picks the template based on the columns.Fields you'll see on every template
A handful of columns appear across all seven templates. Understanding them once saves repeating the explanation.
Column What it means Unique ID*Your own reference for this row. Used for deduplication — re-uploading a row with the same Unique ID updates the record rather than duplicating it. To delete a row, re-upload it with the same Unique ID and set the quantity ( Value) to0— see Edit / update / delete. Pick a stable scheme likeACME-FUEL-2024-03-001and stick with it.Legal Entity (UUID)*The Klappir customer ID this row belongs to. For customers, use your organisation's UUID from the Klappir Platform: Settings → System → Entity IDs (same value as API customerId).For partners, this is the UUID of whichever downstream customer this row applies to. Each row can target a different customer.Supplier Label*Name of the supplier or vendor in your records (e.g. "Summit Fuel Supply"). Used for traceability in Klappir reports.Classification (UNSPSC)*UNSPSC code for the activity — see Classifications for how to choose one. Asset Label*The site, vehicle, or asset the activity is associated with — e.g. "Truck #14","Main Office Building","Wood Piling Road 312".ReferenceOptional free-text reference such as an invoice number. Helps reconcile back to source documents. ClassificationsEvery template needs at least one UNSPSC code per row. Read Classifications before you fill your first CSV — it explains where to look up codes and how many you need per template.
Classifications (UNSPSC) #
Klappir uses UNSPSC codes to tag each row of activity so emissions can be calculated with the right factors. You will see a classification column on almost every CSV template — this section explains what to put there and where to find valid codes.
UNSPSC (United Nations Standard Products and Services Code) is a global taxonomy of products and services. The code you choose tells Klappir what kind of activity the row describes — diesel delivery, mixed waste, a hotel night, and so on — so the platform can apply the correct emission factor.
How many codes per row?
Templates Classification column(s) Count Fuel, upstream transportation & distribution, business travel (all types), goods & services Classification (UNSPSC)orClassification Key (UNSPSC)1 per row Waste Waste type classification (UNSPSC)andWaste treatment classification (UNSPSC)2 per row — what the waste is, and how it was treated Two codes on waste rowsWaste is the only template that needs two UNSPSC values. The same material can have very different emissions depending on treatment (recycling vs landfill vs incineration). See the Waste template for column details.
Where to find UNSPSC codes
Look up codes on the public UNSPSC browser maintained by UNGM:
https://www.ungm.org/public/unspsc
Use the search and hierarchy to find a code that matches your product, fuel type, travel mode, or waste stream. Copy the code into your CSV using the same hyphenated format returned by getClassifications (e.g.
15-10-15-05-K001for diesel).If you later build an API integration, the same taxonomy is available via getClassifications in the GraphQL docs.
Practical rules
- Match the activity — use a fuel code on fuel rows, a travel-related code on travel templates, and so on. A code from the wrong category is a common rejection reason.
- Do not invent codes — if Klappir does not recognise a value, the row will fail validation (see Common errors).
- Stay consistent — once you pick a code for a recurring activity (e.g. diesel for your fleet), reuse it unless the activity type genuinely changes.
Klappir Knowledge BaseHow UNSPSC drives emission factors in the platform — hierarchy, fallbacks, and why granularity matters: How the Klappir Platform uses UNSPSC.
Acme's codesAcme caches diesel as
15-10-15-05-K001and mixed waste keys from the same taxonomy after looking them up on UNGM or via getClassifications. They keep a short internal spreadsheet mapping export categories to keys so finance does not have to search the browser for every row.1Connect Get your credentials #
Klappir provisions SFTP access on request. Email service@klappir.com with your company registration number and the SSH public key you want installed on the server. Klappir does not use passwords for SFTP — only key-based login. You should get connection details back within about one business day.
SSH keys — what to send Klappir
You keep a key pair: a private key (secret, stays on your machine) and a public key (safe to share). Klappir installs the public key on the SFTP server; you authenticate with the private key in your SFTP client.
- If you don't already have a key, generate one on your workstation, for example:
ssh-keygen -t ed25519 -f ~/.ssh/klappir_sftp -C "your-company-sftp"(press Enter to skip a passphrase if your policy allows, or set one and enter it each time in the client). - Open the public file (e.g.
klappir_sftp.pub). It is a single line starting withssh-ed25519orssh-rsa. - Paste that full line into your email to Klappir, or attach only the
.pubfile. Never send the private key file (no.pemwithout.pub, noid_ed25519without.pub). - Use the matching private key in FileZilla / WinSCP / Cyberduck /
sftp -i …when connecting.
Connection details
Hostsftp.automationhub.klappir.ioPort22ProtocolSFTP (SSH File Transfer Protocol)AuthSSH public key only (no password)Usernameyour-registration-numberHome directory/{your-registration-number}/Before you connectHost is always
sftp.automationhub.klappir.io. Your username and home path are assigned when SFTP is provisioned — wait until Klappir confirms your public key is installed before connecting.Quick connection test
Once Klappir has confirmed access, verify the connection from your terminal (replace the key path and user with what Klappir sent you):
# Connect with your private key (-i path must be the private file, not .pub) sftp -i ~/.ssh/klappir_sftp 5501234567@sftp.automationhub.klappir.io # You should land in your home folder sftp> pwd Remote working directory: /5501234567 # List contents — upload production CSVs here (top level of your home) sftp> ls # Example upload sftp> put fuel_2024-03.csv Uploading fuel_2024-03.csv to /5501234567/fuel_2024-03.csv
FileZilla → Site Manager → New Site Protocol: SFTP - SSH File Transfer Protocol Host: sftp.automationhub.klappir.io Port: 22 Logon type: Key file User: 5501234567 Key file: <path to your PRIVATE key, e.g. klappir_sftp> Connect, then drag and drop CSV files into your home folder.
2Templates Fuel #
Liquid and gaseous fuel — diesel, petrol, LPG, marine gas oil, natural gas, biofuels. Use this for fuel consumed by vehicles, generators, boilers, or any combustion equipment. Each row’s
Valueuses one supported unit: L (litres), kg, m³ (m3), or kWh (kwhin the CSV unit column).Download: Fuel template (.xlsx)
Acme's primary templateAcme uploads
fuel_YYYY-MM.csvon the 1st of each month covering the previous month's diesel deliveries to their fleet. A typical month has 200–300 rows split across three downstream customers, distinguished byLegal Entity (UUID).A typical month has 50–80 rows, one per fuel transaction across their truck fleet.Columns
Column Required Description Unique IDrequired Your reference for this row, used for deduplication. E.g. ACME-FUEL-2024-03-001Legal Entity (UUID)required UUID of the Klappir customer this row belongs to. Supplier Labelrequired Name of the fuel supplier (e.g. "Summit Fuel Supply").Daterequired When the fuel was delivered or consumed. ISO 8601 ( 2024-03-01) preferred.Valuerequired Quantity of fuel as a decimal number, e.g. 450.5Unit (l/kg/m3/kwh)required Unit of the value. One of: l(litres, L),kg,m3(cubic metres),kwh(kilowatt-hours, kWh).Fuel descriptionrequired Free-text description, e.g. "Diesel B7","Marine Gas Oil"Customer Accountoptional Your customer account number with the supplier, if relevant. Delivery Methodoptional How the fuel was delivered, e.g. "Tank truck","Forecourt"Address (country)required ISO country code where fuel was delivered, e.g. IS.Classification (UNSPSC)required UNSPSC classification code for the fuel type. Asset Labelrequired The vehicle, site, or asset that consumed the fuel. Referenceoptional Free-text reference, e.g. invoice number. Sample rows — Acme's first three records of March
Unique ID* Legal Entity (UUID)* Supplier Label* Date* Value* Unit* Fuel description* Address* Classification* Asset Label* Reference ACME-FUEL-2024-03-001 afaf2111-f5cc-...28b5d Summit Fuel Supply 2024-03-01 450.5 l Diesel B7 IS 15-10-15-05-K001 Truck #14 Invoice 4521 ACME-FUEL-2024-03-002 afaf2111-f5cc-...28b5d Summit Fuel Supply 2024-03-02 312.0 l Diesel B7 IS 15-10-15-05-K001 Truck #08 Invoice 4522 ACME-FUEL-2024-03-003 afaf2111-f5cc-...28b5d Harbor Energy Co. 2024-03-03 189.7 l Diesel B7 IS 15-10-15-05-K001 Truck #14 Invoice 8841 2Templates Upstream transportation & distribution #
Upstream transportation & distribution — inbound, outbound, and internal logistics. Used by logistics providers, shipping companies, and manufacturers tracking Scope 3 supply chain emissions. Captures origin and destination countries, distance in kilometres, weight in kg, and whether the move is
inbound,outbound, orinternalfrom the customer’s perspective.Download: Upstream transportation & distribution template (.xlsx)
Columns
Column Required Description Unique IDrequired Your row reference. Legal Entity (UUID)required Klappir customer UUID. Supplier Labelrequired Carrier or freight forwarder name. Date (of shipment)required When the shipment occurred. Load Address (country code)required ISO country code of pickup point. Unload Address (country code)required ISO country code of destination. Shipping distance (km)required Distance travelled, in kilometres. Weight (kg)required Mass in kilograms for this upstream transportation & distribution record. Transport Type (inbound/outbound/internal)required inbound,outbound, orinternalfrom the customer’s perspective (internal= moves within the organisation’s own sites).Data Labelrequired Description or label for this shipment. Asset Labelrequired Site or vehicle reference. Classification (UNSPSC)required UNSPSC code for the upstream transportation & distribution category. Referenceoptional Bill of lading or invoice reference. 2Templates Waste #
Waste collected from the customer's operations. Used by waste haulers and environmental service companies, and by direct customers reporting their own waste streams. Records weight, pickup location, plus separate UNSPSC codes for the type of waste and the treatment method (recycling, landfill, incineration, etc.).
Download: Waste template (.xlsx)
Columns
Column Required Description Unique IDrequired Your row reference. Legal Entity (UUID)required Klappir customer UUID. Supplier Labelrequired Waste hauler or treatment provider name. Daterequired Pickup date. Valuerequired Quantity of waste as a decimal. Unit (kg)required Use kg(mass in kilograms).Product Labelrequired Free-text waste description, e.g. "Mixed commercial waste".Pickup address (country)required ISO country code of pickup site. Pickup locationoptional Specific street address or site label. Waste type classification (UNSPSC)required UNSPSC code for the type of waste. Waste treatment classification (UNSPSC)required UNSPSC code for the treatment method (recycling, landfill, incineration, composting, etc.). Asset Labelrequired Site reference. Referenceoptional Pickup ticket or invoice reference. Two classification codes per rowWaste records carry two UNSPSC codes — one for what the waste is (e.g. mixed paper, food waste, hazardous chemicals) and one for how it's treated (recycled, landfilled, incinerated). Both drive the emissions calculation — the same waste type has very different emissions depending on treatment. See Classifications for where to look up codes on UNGM.
2Templates Business Travel — Flight #
Air travel for business purposes. Used by travel agencies, corporate travel management companies, and organisations self-reporting employee flights. Origin and destination airports use IATA three-letter codes (e.g.
KEF,CPH,JFK).Download: Business Travel — Flight template (.xlsx)
Columns
Column Required Description Unique IDrequired Your row reference. Legal Entity (UUID)required Klappir customer UUID. Supplier Labelrequired Airline or travel agency name. Date (of travel)required Date of the flight. From Airport (IATA code)required Origin airport, IATA code (e.g. KEF).To Airport (IATA code)required Destination airport, IATA code. Passengers (count)required Number of passengers on the booking. Travel Descriptionrequired Free-text description, e.g. "Annual conference attendance".Classification (UNSPSC)required UNSPSC code reflecting cabin class. Asset Labelrequired Department, project, or cost-centre label. Referenceoptional PNR or booking reference. 2Templates Business Travel — Surface #
Non-aerial business travel — road, rail, ferry, and sea. Use for taxis, rental cars, employee mileage, trains, ferries, and any combination thereof. Captures origin and destination countries plus distance in kilometres.
Download: Business Travel — Surface template (.xlsx)
Columns
Column Required Description Unique IDrequired Your row reference. Legal Entity (UUID)required Klappir customer UUID. Supplier Labelrequired Taxi company, car rental, rail operator, ferry company, or Employeefor mileage claims.Date (of travel)required Date of the journey. From Country (country code)required ISO country code of origin. To Country (country code)required ISO country code of destination. Distance (km)required Total distance, kilometres. Passenger countrequired Number of passengers. Travel Descriptionrequired Free-text description. Classification (UNSPSC)required UNSPSC code for transport mode. Asset Labelrequired Department or cost-centre. Referenceoptional Booking or expense reference. 2Templates Business Travel — Hotel #
Hotel stays for business purposes. Used by travel agencies and organisations tracking Scope 3 business travel. Country drives the emission factor (hotels in different countries have very different per-night carbon footprints depending on the local energy grid).
Download: Business Travel — Hotel stays template (.xlsx)
Columns
Column Required Description Unique IDrequired Your row reference. Legal Entity (UUID)required Klappir customer UUID. Check in daterequired Date of check-in. Countryrequired ISO country code where the hotel is located. Number of nightsrequired Total nights stayed. Number of roomsrequired Rooms booked. Number of guestsrequired Number of guests across all rooms. Stay descriptionrequired Free-text description, e.g. hotel name. Classification (UNSPSC)required UNSPSC code for accommodation type. Asset Labelrequired Department or cost-centre. Referenceoptional Booking reference. Note: Supplier Label not used hereUnlike most templates, the hotel template does not have a separate
Supplier Labelcolumn — put the hotel name inStay descriptioninstead.2Templates Goods & Services #
Purchased goods and services — Scope 3 Category 1 reporting. Each row represents a procurement record. The most flexible template: you can supply quantity (kg) plus a known emission factor (
kgco2e), or you can supply just the quantity and let Klappir apply a default emission factor based on the UNSPSC classification.Download: Goods & services template (.xlsx)
From the actual Klappir templateThe Goods & Services template ships with two example rows out of the box: a wood piling purchase from "ACME Suppliers Inc" (UNSPSC
30102804) and an apple purchase (UNSPSC50301500). The template uses these to show how the same supplier appears across very different product categories.Columns
Column Required Description Unique IDrequired Your row reference. Legal Entity (UUID)required Klappir customer UUID. Supplier Labelrequired Vendor name. Daterequired Purchase date. Unit (kg)required (one of) Unit of the value, currently kg.Valuerequired (one of) Quantity purchased. kgco2erequired (one of) Direct CO₂e emissions for this purchase, if you have a verified figure from the supplier. kgco2e Source (if applicable)optional Reference for the kgco2e value (EPD, supplier statement, etc.) when supplied. Product Labelrequired Free-text product name, e.g. "Wooden piling".Classification Key (UNSPSC)required UNSPSC code for the product or service category. Asset Labelrequired Site or project the purchase is associated with. Referenceoptional PO or invoice number. Quantity-based or emission-based — supply at least oneMarked with
**in the original template, you must supply either quantity (Unit + Value) or a direct emission figure (kgco2e). If you supply both, Klappir uses your kgco2e value as authoritative and stores the quantity for traceability.Sample rows — straight from the Klappir template
Unique ID* Legal Entity (UUID)* Supplier Label* Date* Unit* Value* kgco2e* Product Label* Classification* Asset Label* 67ef84af80a8 23163150-3244-...80a8 ACME Suppliers Inc 2026-01-15 kg 420.0 — Wooden piling 30102804 Wood Piling Road 312 162843c10333 acde070d-8c4c-...0333 ACME Suppliers Inc 2026-02-07 kg 12.0 — Apples 50301500 Main Office Building 3After upload Edit, update & delete data #
SFTP is your correction channel. You do not edit rows in place on the server — you change the CSV and upload again. Klappir matches each row by
Unique ID(the same identifier asexternalIdin the GraphQL API). Understanding updates and deletions upfront saves painful rework later.Processing time (SLA)
Klappir processes uploaded files within 24 hours of file upload. After that window, accepted rows appear in the Klappir platform for the organisation and period you expect. Plan spot-checks (see View and validate data) after each upload, not only on the first run.
Update an existing row
To change a record that already ingested successfully:
- Keep the same
Unique IDas the original row. - Change any other columns that need correcting (date, quantity, classification, supplier, and so on).
- Upload a CSV that includes the corrected row — it can be the full export again or a small file with only the changed rows, as long as headers match the template.
Klappir treats the new file as an update for that
Unique ID, not a second copy. This is the same deduplication behaviour described in CSV format basics.GraphQL equivalentRe-submit a mutation record with the same
externalIdto update it, or set quantity fields to zero to delete. See Edit / update / delete in the GraphQL docs.Delete a row
To remove a record from Klappir, upload a row with the same
Unique IDand set the quantity column to zero:- Fuel, upstream transportation & distribution, waste, goods & services — set
Valueto0. - Business travel templates — set the primary quantity field to
0(for example distance, nights, or passenger count as defined in that template).
Do not delete the row from your CSV and expect Klappir to infer removal — absence from a new file does not delete prior data. The zero-quantity row is the deletion signal.
Recommended correction workflow
Step Action 1 Confirm the original row ingested — View and validate data. 2 Fix the source export or build a minimal correction CSV with the same template headers. 3 Re-upload; wait up to 24 hours for processing. 4 Validate again in the platform. If the row still looks wrong, check Common errors or contact service@klappir.com. Acme fixes a typoAcme discovers
ACME-FUEL-2024-03-002used the wrong date. They change one cell, re-uploadfuel_2024-03-correction.csvwith that single row (same headers as the Fuel template), and confirm in the platform the next day that March activity shows the corrected date.3After upload View and validate data #
Uploading is only half the job. Use the Klappir platform to confirm that rows were accepted and appear under the right legal entity and time range. Pair that with the error patterns in this guide when something does not show up as expected.
When to check
Allow up to 24 hours after file upload for Klappir to process the file. If nothing changes after that, treat it as an ingestion or validation issue rather than normal delay.
What to verify in the Klappir platform
- Organisation — activity is attributed to the legal entity matching
Legal Entity (UUID)in the CSV (for customers, your own UUID; for partners, each downstream customer). - Time period — dates on rows map to the reporting period you expect (monthly uploads should appear in that month).
- Volume — row counts and totals are in the right ballpark compared to your source system (fuel litres, waste kg, travel segments, and so on).
- Classification — categories line up with the UNSPSC keys you submitted; unexpected gaps often mean rejected rows.
Interpreting validation outcomes
What you see Likely meaning What to do Data appears as expected within 24 hours File and rows accepted No action — continue your regular export schedule. Partial data — some periods or sites missing Some rows failed validation while others succeeded Compare against your CSV; fix rejected rows and re-upload. See Common errors. Nothing new after 24 hours File-level rejection, wrong folder, or transfer never completed Check SFTP client logs, filename, and template headers. Re-upload after fixing. Duplicates or doubled totals Changed Unique IDscheme or re-sent overlapping exportsStabilise IDs; use Edit / update / delete to correct instead of inventing new IDs for the same activity. Garbled characters in labels Encoding issue Re-save as UTF-8 CSV and upload again. Row-level vs file-level failuresRow-level problems (bad date, unknown UNSPSC, wrong unit) usually affect only the offending rows — the rest of the file can still process. File-level problems (wrong headers, empty file, corrupt encoding) reject the entire upload. The Common errors section lists both.
3After upload Common errors #
Most rejection reasons fall into a small handful of categories. Once you've seen each once, you'll fix them in seconds. When the pipeline rejects rows or a whole file, the reason is usually tied to one of the patterns below — match your symptom, apply the fix, and re-upload.
The frequent flyers
Error Cause Fix Header row not recognised Column names don't match any template, often because of an extra space or different capitalisation. Re-download the official template from the matching template page and copy your data into it. Don't modify the header row. Required field missing A column marked *is blank.Either fill the cell or delete the row entirely. Invalid country code Spelled out ( "Iceland") or three-letter ("ISL") instead of ISO alpha-2 ("IS").Use ISO 3166-1 alpha-2: 2-letter codes only. IS,DK,NO,US.Date couldn't be parsed Mixed format ( 03/01/24), Excel serial number, or an impossible date like2024-13-05.Use ISO 8601: 2024-03-01. If you must use a national format, be consistent across the whole file.Decimal separator European comma decimals ( 123,45) instead of period (123.45).Configure your CSV export to use period as the decimal separator. In Excel, this is the regional setting. Unknown UNSPSC code Code doesn't exist or has been retired in the current UNSPSC version. Look up a current code on UNGM UNSPSC, or use getClassifications if you integrate via API. Your account manager can help during onboarding. UUID doesn't match an authorized customer The Legal Entity (UUID)column contains an ID that isn't yours, or — for partners — a customer that hasn't authorized your integration.For customers: confirm you're using the UUID Klappir gave you during onboarding. For partners: the customer needs to enable your integration in their Klappir account first. File encoding Special characters (Icelandic, Nordic, accented) appear garbled. Save as UTF-8. In Excel: File → Save As → CSV UTF-8 (Comma delimited). Avoid the plain "CSV" option which uses Windows-1252. Comma inside a quoted value Address contains a comma but isn't quoted. Wrap any value containing a comma in double quotes: "Borgartún 21, 105 Reykjavík".Unit not in allowed list Used a unit not listed in the template, e.g. gallons in the Fuel template. Convert to one of the allowed units before upload. The Fuel template accepts l,kg,m3, orkwhonly (L, kg, m³, kWh).If a whole file is rejected
A small number of conditions cause Klappir to reject the entire file rather than process row by row:
- The file isn't a recognised CSV (binary content, wrong extension, corrupted).
- The header row doesn't match any of the seven templates.
- The file is empty or has no data rows after the header.
- The file is implausibly large — uploads over 50,000 rows are flagged for Klappir review before processing.
In all of these cases Klappir rejects the file at ingest time with a clear file-level reason (or Klappir can confirm what happened). Fix and re-upload — there's nothing to clean up on Klappir's side.
Running in production #
Once your first upload works end-to-end, the next step is making this part of your routine without having to think about it.
Schedule the export
Most customers run their CSV export on a fixed cadence — daily, weekly, or monthly. Use cron, Windows Task Scheduler, or your ERP's built-in scheduler to drop files onto the SFTP server automatically.
Pick a stable Unique ID scheme
Acme uses
ACME-FUEL-2024-03-001— prefix, type, year-month, sequential. Once you commit to a scheme, don't change it. Future you will thank you when reconciling historical data.Confirm data in Klappir
After each upload, allow up to 24 hours for processing, then spot-check in the Klappir platform that new activity appears as you expect for the right organisation and time period. See View and validate data.
Keep SFTP client and scheduler logs
Your SFTP client or automation logs show whether a transfer completed. If a file never left your side, the issue is local (path, key, network) before it reaches Klappir.
Keep originals locally
Klappir archives processed files internally for traceability, but you should keep your own copies of every file you upload. They're your audit trail for the export, separate from Klappir's record of ingestion.
Refresh the UNSPSC list
The classification taxonomy expands over time. If you're hard-coding codes in your export, revisit them every 6–12 months using the UNGM UNSPSC browser or your Klappir account manager.
When something goes wrong
Email service@klappir.com with the timestamp of the upload attempt, the filename, what you see in the Klappir platform (if anything), and any error text from your SFTP client or scheduler logs. The Klappir team can usually help within a business day.
Want to switch to GraphQL later?
If you grow into a use case that needs real-time submission, programmatic batching, or fine-grained error handling, the GraphQL API is the next step up. Same data, same classifications, same customer model — different transport.
Klappir GraphQL API Reference
A single GraphQL endpoint for submitting data into the Klappir platform. The same data types and field concepts as the SFTP CSV templates — see SFTP and API field alignment.
Acme Logistics — a freight and waste-haulage company in Reykjavík with registration number 5501234567. Every code example you see uses Acme's setup: customerId: afaf2111-f5cc-4c69-b806-46d318e28b5d, classificationKey 15-10-15-05-K001 (diesel), and externalIds prefixed ACME-.
Customer scenarios show Acme uploading its own diesel and waste data.Partner scenarios show Acme submitting upstream transportation & distribution and fuel data on behalf of its three downstream customers.
Endpoint: https://api.klappir.com/graphql
Required headers on every request:
{
"Content-Type": "application/json",
"x-api-key": "your-api-key-here"
}
No API key yet? Email service@klappir.com to request one. If you'd rather avoid building an integration entirely, see the SFTP path.
- 1Get an API key from KlappirEmail service@klappir.com to request a key. You pass it on every request as the
x-api-keyheader (details in the Getting started section). - 2Find your organisation UUIDIn the Klappir Platform, open Settings → System → Entity IDs — the value you can see is your
customerIdon every mutation. - 3Look up classification keysFetch the getClassifications codes that match the activity types you'll be sending.
- 4Submit your activity recordsUse the create* mutation matching your data — fuel, waste, business travel, upstream transportation & distribution, goods and services.
- 5Correct or remove rows when neededRe-submit with the same
externalIdto update; set quantity fields to0to delete — see Edit / update / delete.
- 1Get an API key from KlappirEmail service@klappir.com to request a key. You pass it on every request as the
x-api-keyheader (details in the Getting started section). - 2Discover authorized customersCall getActiveCustomers to see which Klappir customers have enabled your integration. Each
idin the response is thecustomerIdfor that customer's records. Refresh regularly. - 3Look up classification keysFetch the getClassifications codes that match the activity types you'll be sending.
- 4Submit your activity recordsUse the create* mutation matching your data — fuel, waste, business travel, upstream transportation & distribution, goods and services. Each record carries a
customerId, so one batch can cover many customers at once. - 5Correct or remove rows when neededRe-submit with the same
externalIdto update; set quantity fields to0to delete — see Edit / update / delete.
Getting started #
The Klappir Public API is a single GraphQL endpoint. All requests use HTTP POST with Content-Type: application/json. There are two operation types:
query
Read data from Klappir — for example classification keys and other reference data you need before submitting records about your own organisation.Read data from Klappir — customers you're authorized to send data for, valid classifications, and related metadata.
mutation
Write data to Klappir — submit fuel, waste, travel, upstream transportation & distribution, and other sustainability records.
Every request body is a JSON object with two keys:
| Key | Required | Description |
|---|---|---|
query | required | A string containing the GraphQL operation. |
variables | optional | A JSON object with input parameters referenced inside the query string. |
In GraphQL, ! marks a field as non-null (required). For example, CreateFuelInput! means the input argument must be provided.
The examples below walk through a first successful read for your audience. If a request fails before that works, jump to Troubleshooting common API errors at the end of this section.
Acme's first call
Acme Logistics already has customerId from the Klappir Platform (Settings → System → Entity IDs). Before the first fuel mutation they fetch valid classification keys:
{
"query": "query { getClassifications(input: { dataType: [FUEL] }) { key level dataType usage description } }"
}{
"data": {
"getClassifications": [
{
"key": "15-10-15-05-K001",
"level": 5,
"dataType": "FUEL",
"usage": "General classification",
"description": "Diesel 100% mineral"
},
{
"key": "15-10-15-06-K001",
"level": 5,
"dataType": "FUEL",
"usage": "General classification",
"description": "Petrol 100% mineral"
}
]
}
}They pick a key from the response for classificationKey on each record. Full options and filters: getClassifications. Every mutation example in this guide uses customerId: afaf2111-f5cc-4c69-b806-46d318e28b5d — the same UUID Acme sees in the Platform.
Acme's first call
Acme integrates as a partner. Their first read lists Klappir customers that have authorized their integration:
{
"query": "query { getActiveCustomers { id, name, registration } }"
}{
"data": {
"getActiveCustomers": [
{
"id": "afaf2111-f5cc-4c69-b806-46d318e28b5d", // downstream customer id
"name": "Acme Logistics",
"registration": "5501234567"
}
]
}
}Each id is the customerId to set on records for that customer. Acme caches the list and refreshes it on a schedule. Partner flows also use getClassifications before submitting.
Troubleshooting common API errors
If a request fails, use this table to diagnose quickly and apply the exact fix.
| What you see | Likely cause | Exact fix |
|---|---|---|
401 Unauthorized | Missing or invalid x-api-key header. | Send both headers on every request: Content-Type: application/json and x-api-key: <your-key>. If it still fails, request a new or rotated key from service@klappir.com. |
| GraphQL validation error (for example: required field missing, wrong type) | Payload does not match mutation input type (for example missing time, string passed where number is required). | Check the mutation's input type and ensure every required field is present with correct type. Start from the doc's example payload, then add fields incrementally. |
customerId missing / null / unauthorized-customer style error | Record has no customerId, or ID is not currently authorized for your integration. | Use the UUID from Platform Settings → System → Entity IDs (must match your organisation).Fetch getActiveCustomers, pick an id from that result, and set it on every record. Refresh this list regularly before submission. |
Invalid or unknown classificationKey | Classification key is outdated, misspelled, or for the wrong data type. | Call getClassifications and select a current key for your data type. Do not hard-code old values; refresh your classification cache on a schedule. |
Your journey, in two stages #
Every Klappir API integration moves through the same two stages, in order. The sidebar groups operations by stage so you always know where you are.
Stage 1 — Discover
Your customerId is your organisation UUID in the Klappir Platform: Settings → System → Entity IDs. Use getClassifications for valid classification keys on each record.
Find out who you're sending data for and how that data should be classified. getActiveCustomers tells you which customers you're authorized to submit data for. getClassifications tells you the valid codes for tagging each record.
Stage 2 — Submit data
Pick the create* mutation that matches your data type and submit records in batches. Every record carries your organisation's customerId.
Pick the create* mutation that matches your data type and submit records in batches. Each record carries a customerId for the customer it belongs to, so one batch can cover many customers at once. You'll come back to this stage continuously as new data flows in.
The API only accepts data for customers who have explicitly granted your integration access. getActiveCustomers returns only currently authorized customers. If a customer isn't in the list, you can't send data for them yet.
As a direct customer, copy your organisation UUID from the Klappir Platform: Settings → System → Entity IDs — that's your customerId in every mutation.
Sending data, in concept #
Before diving into specific mutations, three fields appear on nearly every record. Understanding them once will save you a lot of confusion later.
In the GraphQL sections below, field tables include a Req column: required (green) means non-null in the schema or a mandatory nested input; optional (gray) means you may omit the field. Nullable quantity fields can still require at least one value per row — see each mutation’s note above its table.
| Req | Field | Type | Description |
|---|---|---|---|
| required | customerId | String! | Your organisation's UUID from the Klappir Platform: Settings → System → Entity IDs.The id from getActiveCustomers for the customer this row belongs to. Routes the record to the correct Klappir account. |
| required | classificationKey | String! | A UNSPSC-based key from getClassifications. Determines how the record is categorized in emissions calculations. |
| required | time | DateTime! | ISO 8601 timestamp for when the activity occurred. Always UTC. |
| optional | externalId | String | Your own reference ID (required on each mutation input row). Same role as Unique ID on SFTP — re-submitting with the same externalId updates the record; zero quantity removes it. See Edit / update / delete. |
Classifications, briefly
Klappir uses a classification system built on UNSPSC codes. Each record needs one. Call getClassifications without input for all keys your integration supports, or pass dataType: ["FUEL", …] (an array) to narrow.
Acme deals primarily in diesel and mixed waste. They cache key values from getClassifications on first run (e.g. diesel under dataType: ["FUEL"]). They refresh the cache weekly.
Always fetch the latest list from getClassifications rather than hard-coding — the taxonomy expands regularly.
externalId — your deduplication anchor
The externalId is your record's identity from your system's perspective — the API equivalent of SFTP Unique ID. Acme uses a scheme like ACME-FUEL-2024-03-001 (prefix, type, year-month, sequential). Pick a scheme and stick with it. Corrections and deletions use the same rules as the SFTP path; see Edit / update / delete.
Mutation quick reference
Seven create-style mutations, one per data category. Use the one that matches what you have:
| Mutation | Data type | Key quantity field(s) |
|---|---|---|
createFuel | Fuel consumption | Any combination of liters (L), kg, m3, kwh per row |
createCargo | Upstream transportation & distribution | transportType, date, addresses, distance, weight |
createWaste | Waste disposal | kg |
createFlightTravel | Air travel | origin, destination, cabin class, passengers |
createSurfaceTravel | Road / rail / sea travel | distanceTraveled, addresses, passengers |
createHotelTravel | Hotel stays | nights, country |
createGoodsServices | Purchased goods & services | productLabel, classificationKey, at least one of kg / kgco2e |
SFTP and API field alignment #
The GraphQL API and SFTP CSV templates describe the same data. Data types, classification keys, customer routing, and deduplication rules align — only the transport differs (HTTP mutations vs file upload). If you know one path, you can map directly to the other.
Shared concepts
| Concept | SFTP (CSV) | GraphQL API |
|---|---|---|
| Customer routing | Legal Entity (UUID) | customerId |
| Row identity / deduplication | Unique ID | externalId |
| Classification | Classification (UNSPSC) or template-specific UNSPSC columns | classificationKey (and wasteClassificationKey / treatmentClassificationKey for waste) |
| Activity date | Date (ISO 8601) | time (DateTime!, UTC) |
| Update existing row | Re-upload same Unique ID | Re-submit same externalId |
| Delete row | Same Unique ID, quantity 0 | Same externalId, zero quantity fields |
| Processing SLA (batch) | Within 24 hours of file upload | Mutations are processed on request; responses are immediate |
Data type ↔ mutation ↔ template
| Data type | SFTP template | GraphQL mutation |
|---|---|---|
| Fuel | Fuel | createFuel |
| Upstream transportation & distribution | Upstream transportation & distribution | createCargo |
| Waste | Waste | createWaste |
| Business travel — flight | Business Travel — Flight | createFlightTravel |
| Business travel — surface | Business Travel — Surface | createSurfaceTravel |
| Business travel — hotel | Business Travel — Hotel | createHotelTravel |
| Goods & services | Goods & Services | createGoodsServices |
Updates and deletions follow the same rules on both paths — see Edit / update / delete (API) and Edit / update / delete (SFTP). Classifications come from the same UNSPSC taxonomy — SFTP: Classifications, getClassifications in the API. See also the Klappir Knowledge Base article on UNSPSC.
1Discover QUERY getActiveCustomers #
List Klappir customers your integration may send data for. This query returns every customer that has enabled your integration. Refresh regularly — authorization can change overnight.
Acme calls this nightly. Today it returns three customers — Reykjavík Hospital, Northwind Brewery, and Lighthouse Studios. Tomorrow it might return four if a new customer enabled the integration overnight.
Returns [Customer!]! — no arguments required
Query
query getActiveCustomers { getActiveCustomers { id name registration description address { country address city postalCode } } }
Response — Acme's view
{
"data": {
"getActiveCustomers": [
{
"id": "afaf2111-f5cc-4c69-b806-46d318e28b5d",
"name": "Acme Logistics",
"registration": "5501234567",
"description": null,
"address": {
"country": "IS",
"address": "Borgartún 21",
"city": "Reykjavík",
"postalCode": "105"
}
}
]
}
}
1Discover QUERY getClassifications #
Fetch valid classification keys for tagging your data records. Every record you submit needs a classificationKey. This query returns the available codes.
Acme filters with dataType: [FUEL] on first run, finds the diesel key (e.g. 15-10-15-05-K001) and caches it. Their nightly job refreshes the cache once a week.
Returns [Classification!]!
Arguments
| Req | Argument | Type | Description |
|---|---|---|---|
| optional | input | GetClassificationsInput | Optional. Filter by dataType — an array of DataTypeEnum values (e.g. [FUEL]) — and/or keyPrefix string. Omit input to return classifications for all data types your integration supports. |
Query
query getClassifications($input: GetClassificationsInput) { getClassifications(input: $input) { key level dataType usage description } }
Response fields (Classification)
| Req | Field | Type | Description |
|---|---|---|---|
| required | key | ID! | Klappir classification ID — copy verbatim into classificationKey (or waste keys) on mutations. Hyphen-separated hierarchy (e.g. 15-10-15-05-K001), not compact UNSPSC digits. |
| required | level | Int! | Depth in the classification hierarchy (higher = more specific). |
| required | dataType | DataTypeEnum! | Which mutation family this key applies to (e.g. FUEL, WASTE). |
| required | usage | String! | How Klappir expects the key to be used (internal label). |
| required | description | String! | Human-readable label for integrators. |
Variables — Acme filtering for fuel
{ "input": { "dataType": ["FUEL"] } }
dataType is a list in the schema — use an array even for one type, e.g. ["FUEL", "WASTE"] to prefetch keys for multiple templates.
Response
{
"data": {
"getClassifications": [
{
"key": "15-10-15-05-K001",
"level": 5,
"dataType": "FUEL",
"usage": "General classification",
"description": "Diesel 100% mineral"
},
{
"key": "15-10-15-06-K001",
"level": 5,
"dataType": "FUEL",
"usage": "General classification",
"description": "Petrol 100% mineral"
}
]
}
}
Sample rows (truncated). Your environment returns the full list — always copy key values from your own response into classificationKey.
2Submit MUTATION createFuel #
Submit fuel consumption records. Use this for any liquid or gaseous fuel — petrol, diesel, marine gas oil, natural gas, LPG, biofuels — consumed by vehicles, generators, boilers, or other equipment.
Acme runs createFuel nightly with the previous day's diesel deliveries. Each batch contains records for all three of their downstream customers, distinguished by customerId on each record.A typical nightly batch is 50–200 records, one per fuel transaction across their fleet.
Returns CreateDataPayload!
Input: CreateFuelInput! → data: [CreateSingleFuelInput!]!
You can submit one or many fuel records in data. For each record you may set one or several quantity fields among liters (litres, L), kg (kilograms), m3 (cubic metres, m³), and kwh (kilowatt-hours, kWh) — for example both liters and kg when your data includes both.
| Req | Field | Type | Description |
|---|---|---|---|
| required | externalId | String! | Your unique row ID (deduplication) |
| required | time | DateTime! | When the fuel was consumed (UTC) |
| required | customerId | String! | Target Klappir customer ID |
| required | classificationKey | String! | UNSPSC key from getClassifications |
| required | assetDescription | String! | Asset tied to the activity (vehicle, site, etc.). Min. length enforced by API. |
| required | fuelDescription | String! | Fuel product (e.g. Diesel B7). Min. length enforced by API. |
| optional | supplierLabel | String | Supplier display name |
| optional | deliveryPoint | String | Delivery point or supplier reference (truck reg., station id, vessel IMO, etc.) |
| required | address | AddressInput! | Station or delivery address (see AddressInput) |
| optional | liters | Float | Volume in litres (L) |
| optional | kg | Float | Mass in kilograms |
| optional | m3 | Float | Volume in cubic metres (m³) |
| optional | kwh | Float | Energy in kilowatt-hours (kWh) |
| optional | reference | String | Invoice or internal reference |
Mutation
mutation createFuel($input: CreateFuelInput!) { createFuel(input: $input) { success message rows } }
Variables — Acme submitting one diesel record
{
"input": {
"data": [{
"externalId": "ACME-FUEL-2024-03-001",
"time": "2024-03-01T08:00:00.000Z",
"customerId": "afaf2111-f5cc-4c69-b806-46d318e28b5d",
"classificationKey": "15-10-15-05-K001",
"assetDescription": "Truck #14",
"fuelDescription": "Diesel B7",
"address": {
"country": "IS",
"city": "Reykjavík",
"address": "12 Harbor Lane",
"postalCode": "105"
},
"liters": 450.5,
"kg": 382.0,
"reference": "Invoice #4521"
}]
}
}
Response
{"data": {"createFuel": {"success": true, "message": "OK", "rows": 1}}}
2Submit MUTATION createCargo #
Submit upstream transportation & distribution records. Use for inbound, outbound, or internal logistics moves by road, sea, rail, or air. In the API this mutation is still named createCargo; input is data, an array of CreateSingleCargoInput.
For upstream transportation & distribution, Acme tags every move as OUTBOUND from their depot or INBOUND when receiving stock from suppliers. Their fleet management system already has this distinction.
Returns CreateDataPayload!
Fields on CreateSingleCargoInput
Use AddressInput for loadAddress and unloadAddress (see AddressInput).
| Req | Field | Type | Description |
|---|---|---|---|
| required | externalId | String! | Your unique row ID |
| required | date | DateTime! | Date of shipment (UTC) |
| required | loadAddress | AddressInput! | Origin / pickup |
| required | unloadAddress | AddressInput! | Destination / delivery |
| required | distance | Float! | Distance in kilometres |
| required | weight | Float! | Mass in kilograms for this upstream transportation & distribution record |
| required | customerId | String! | Target customer ID |
| required | classificationKey | String! | UNSPSC classification key |
| required | transportType | CargoTransportTypeEnum! | INBOUND, OUTBOUND, or INTERNAL |
| required | assetLabel | String! | Asset tied to the shipment (vehicle, site, etc.) |
| required | dataLabel | String! | Distinct label for the move (B/L, booking ref., etc.) |
| optional | supplierLabel | String | Carrier or forwarder name |
| optional | reference | String | Extra context (invoice, notes) |
Mutation
mutation createCargo($input: CreateCargoInput!) { createCargo(input: $input) { success message rows } }
Variables — Acme outbound trailer (same day)
{
"input": {
"data": [{
"externalId": "ACME-UTD-2024-03-088",
"date": "2024-03-08T06:00:00.000Z",
"loadAddress": {
"country": "IS",
"city": "Reykjavík",
"address": "Acme depot, Sundagarðar",
"postalCode": "104"
},
"unloadAddress": {
"country": "IS",
"city": "Keflavík",
"address": "Keflavík logistics terminal",
"postalCode": "235"
},
"distance": 52,
"weight": 18500,
"customerId": "afaf2111-f5cc-4c69-b806-46d318e28b5d",
"classificationKey": "78121603",
"transportType": "OUTBOUND",
"assetLabel": "Trailer T-402",
"dataLabel": "B/L ICE-2024-7781",
"supplierLabel": "Northwind Line",
"reference": "INV 99204"
}]
}
}
Response
{"data": {"createCargo": {"success": true, "message": "OK", "rows": 1}}}
2Submit MUTATION createWaste #
Submit waste disposal records. For waste haulers and organisations self-reporting waste data. Note: createWaste returns CreateWastePayload with a status enum, unlike most mutations which return a success boolean.
Waste records carry two UNSPSC codes — one for what the waste is (e.g. mixed paper, food waste, hazardous chemicals) and one for how it's treated (recycled, landfilled, incinerated). Both drive the emissions calculation — the same waste type has very different emissions depending on treatment.
In createWaste these map to wasteClassificationKey (what the waste is) and treatmentClassificationKey (how it is treated) on each CreateSingleWasteInput row — the same split as in the Waste CSV template.
Returns CreateWastePayload! — note: different payload type
Fields on CreateSingleWasteInput
You can submit one or many waste records in data. Quantity is reported as mass in kilograms using the kg field (cubic metres / m3 are not supported for waste). Use AddressInput for the collection / pickup site (see AddressInput).
| Req | Field | Type | Description |
|---|---|---|---|
| required | externalId | String! | Your unique row ID |
| required | time | DateTime! | Pickup time (UTC) |
| required | wasteDescription | String! | What was collected or disposed |
| required | customerId | String! | Target customer ID |
| required | wasteClassificationKey | String! | UNSPSC key for the waste stream (from getClassifications) |
| required | treatmentClassificationKey | String! | Treatment / disposal UNSPSC key |
| required | kg | Float! | Mass in kilograms for this row (m3 is not supported for waste) |
| required | pickUpAddress | AddressInput! | Customer / collection site |
| optional | pickUpLocation | String | Extra location detail (gate, bay, instructions) |
| optional | supplierLabel | String | Waste pickup provider name |
| optional | reference | String | Ticket, weighbridge, or invoice ref. |
Mutation
mutation createWaste($input: CreateWasteInput!) { createWaste(input: $input) { status rows } }
Variables — Acme's mixed waste pickup
{
"input": {
"data": [{
"externalId": "ACME-WASTE-202403-001",
"time": "2024-03-05T07:30:00.000Z",
"wasteDescription": "Mixed municipal — Reykjavík depot",
"customerId": "afaf2111-f5cc-4c69-b806-46d318e28b5d",
"wasteClassificationKey": "21-07-01",
"treatmentClassificationKey": "77-10-20",
"pickUpAddress": { "country": "IS", "city": "Reykjavík" },
"kg": 1200
}]
}
}
Response
{"data": {"createWaste": {"status": "SUCCESS", "rows": 1}}}
2Submit MUTATION createFlightTravel #
Submit air travel records. For travel agencies, corporate travel management, and organisations self-reporting flight emissions. Captures origin/destination airports, cabin class, and number of passengers.
Returns CreateDataPayload!
Fields on CreateSingleFlightTravelInput
Cabin or fare class is usually encoded in the UNSPSC classificationKey you choose from getClassifications.
| Req | Field | Type | Description |
|---|---|---|---|
| required | externalId | String! | Your unique row ID |
| required | dateOfTravel | DateTime! | Flight date/time (UTC) |
| required | fromIATA | String! | Origin airport (IATA) |
| required | toIATA | String! | Destination airport (IATA) |
| required | numberOfPassengers | Int! | Passenger count |
| required | travelDescription | String! | Trip purpose or label |
| required | assetDescription | String! | Asset tied to the activity (vehicle, site, etc.) |
| required | customerId | String! | Target customer ID |
| optional | classificationKey | String | UNSPSC travel classification |
| optional | supplierLabel | String | Agency or TMC name |
| optional | reference | String | PNR, invoice, or policy ref. |
Mutation
mutation createFlightTravel($input: CreateFlightTravelInput!) { createFlightTravel(input: $input) { success message rows } }
Variables — Acme leg KEF → CPH
{
"input": {
"data": [{
"externalId": "ACME-FLT-2024-03-014",
"dateOfTravel": "2024-03-12T14:20:00.000Z",
"fromIATA": "KEF",
"toIATA": "CPH",
"numberOfPassengers": 1,
"travelDescription": "Client meeting — Copenhagen",
"assetDescription": "Sales team / EMEA",
"customerId": "afaf2111-f5cc-4c69-b806-46d318e28b5d",
"classificationKey": "40101501",
"reference": "PNR ABC123"
}]
}
}
Response
{"data": {"createFlightTravel": {"success": true, "message": "OK", "rows": 1}}}
2Submit MUTATION createSurfaceTravel #
Submit non-aerial transportation records. Covers road, rail, and sea travel. Use for company vehicle mileage, employee commute, ferry trips, or any surface-mode transport.
Returns CreateDataPayload!
Fields on CreateSingleSurfaceTravelInput
Surface travel covers road, rail, and sea modes (for example company vehicles, buses, trains, or ferries). Use AddressInput for fromAddress and toAddress (see AddressInput).
| Req | Field | Type | Description |
|---|---|---|---|
| required | externalId | String! | Your unique row ID |
| required | dateOfTravel | DateTime! | Trip start (UTC) |
| required | fromAddress | AddressInput! | Origin |
| required | toAddress | AddressInput! | Destination |
| required | distanceTraveled | Float! | Distance in kilometres |
| required | numberOfPassengers | Int! | Passenger count |
| required | travelDescription | String! | Mode and context (e.g. ferry, rail commute) |
| required | assetDescription | String! | Asset tied to the activity (vehicle, site, etc.) |
| required | customerId | String! | Target customer ID |
| optional | classificationKey | String | UNSPSC surface-travel key |
| optional | supplierLabel | String | Operator name |
| optional | reference | String | Ticket or invoice ref. |
Mutation
mutation createSurfaceTravel($input: CreateSurfaceTravelInput!) { createSurfaceTravel(input: $input) { success message rows } }
Variables — Acme ferry leg (road + sea)
{
"input": {
"data": [{
"externalId": "ACME-SURF-2024-03-021",
"dateOfTravel": "2024-03-15T07:00:00.000Z",
"fromAddress": {
"country": "IS",
"city": "Landeyjahöfn",
"label": "Ferry terminal"
},
"toAddress": {
"country": "IS",
"city": "Vestmannaeyjar",
"label": "Herjólfur dock"
},
"distanceTraveled": 14,
"numberOfPassengers": 2,
"travelDescription": "Ferry — staff site visit",
"assetDescription": "Field operations / South",
"customerId": "afaf2111-f5cc-4c69-b806-46d318e28b5d",
"classificationKey": "78121601",
"supplierLabel": "Herjólfur",
"reference": "Ticket H-44102"
}]
}
}
Response
{"data": {"createSurfaceTravel": {"success": true, "message": "OK", "rows": 1}}}
2Submit MUTATION createHotelTravel #
Submit hotel stay records. For travel agencies and organisations tracking Scope 3 business travel. Each record captures number of nights, country, and optionally hotel name and city.
Returns CreateDataPayload!
Fields on CreateSingleHotelTravelInput
address identifies the country where the hotel is located (see AddressInput).
| Req | Field | Type | Description |
|---|---|---|---|
| required | externalId | String! | Your unique row ID |
| required | date | DateTime! | Check-in date (UTC) |
| required | address | AddressInput! | Hotel location (at least country) |
| required | numberOfNights | Int! | Nights booked |
| required | numberOfRooms | Int! | Rooms booked |
| optional | numberOfGuests | Int | Guests (optional if unknown) |
| required | stayDescription | String! | Free-text description of the stay — for example the hotel name, a conference or event, the customer or account you visited, or an internal project label. |
| required | assetDescription | String! | Asset tied to the activity (vehicle, site, etc.) |
| required | customerId | String! | Target customer ID |
| optional | classificationKey | String | UNSPSC hotel stay key |
| optional | supplierLabel | String | Chain or booking channel |
| optional | reference | String | Confirmation or invoice ref. |
Mutation
mutation createHotelTravel($input: CreateHotelTravelInput!) { createHotelTravel(input: $input) { success message rows } }
Variables — Acme two-night stay in Copenhagen
{
"input": {
"data": [{
"externalId": "ACME-HOTEL-2024-03-009",
"date": "2024-03-18T15:00:00.000Z",
"address": {
"country": "DK",
"city": "Copenhagen",
"address": "Tivoli Hotel & Congress Center",
"postalCode": "1630"
},
"numberOfNights": 2,
"numberOfRooms": 1,
"numberOfGuests": 1,
"stayDescription": "Tivoli Hotel — client workshop (EMEA sales)",
"assetDescription": "Sales team / EMEA",
"customerId": "afaf2111-f5cc-4c69-b806-46d318e28b5d",
"classificationKey": "90111501",
"supplierLabel": "CorporateTravel.dk",
"reference": "Conf. DK-88331"
}]
}
}
Response
{"data": {"createHotelTravel": {"success": true, "message": "OK", "rows": 1}}}
2Submit MUTATION createGoodsServices #
Submit purchased goods and services data. Covers Scope 3 Category 1 spend-based reporting. Each record represents monetary spend on a product or service — Klappir converts this to an emissions estimate using spend-based emission factors.
Returns CreateDataPayload!
Fields on CreateSingleGoodsServicesInput
Other members on CreateSingleGoodsServicesInput may exist beyond this table — confirm names and nullability with GraphQL introspection for your environment.
Each record must include at least one of kg (kilograms purchased) and kgco2e (supplier-verified CO₂e for the line item); you may send both. When both are present, Klappir uses kgco2e as the authoritative emissions value and keeps kg for traceability — the same rule as the Goods & Services CSV template.
| Req | Field | Type | Description |
|---|---|---|---|
| required | externalId | String! | Your unique row ID |
| required | date | DateTime! | Purchase / invoice date (UTC) |
| required | productLabel | String! | Product or line item as on the invoice |
| required | assetDescription | String! | Asset tied to the activity (vehicle, site, etc.) |
| required | classificationKey | String! | UNSPSC key from getClassifications |
| required | customerId | String! | Target customer ID |
| optional | kg | Float | Mass purchased in kilograms (quantity path). See the note above for row-level requirements. |
| optional | kgco2e | Float | Direct CO₂e for the line item from a supplier-verified figure (emission path). When both kg and kgco2e are sent, kgco2e is authoritative for emissions. |
| optional | supplierLabel | String | Vendor display name |
| optional | reference | String | PO, contract, or invoice ref. |
Mutation
mutation createGoodsServices($input: CreateGoodsServicesInput!) { createGoodsServices(input: $input) { success message rows } }
Variables — Acme quantity + supplier CO₂e (both)
{
"input": {
"data": [{
"externalId": "ACME-GS-2024-03-044",
"date": "2024-03-22T00:00:00.000Z",
"productLabel": "Office paper — A4, recycled 80gsm",
"assetDescription": "HQ — Borgartún",
"classificationKey": "14111507",
"customerId": "afaf2111-f5cc-4c69-b806-46d318e28b5d",
"kg": 120,
"kgco2e": 86.4,
"supplierLabel": "Nordic Office Supply",
"reference": "PO 77812"
}]
}
}
You can omit kgco2e and send only kg (or only kgco2e if you have a verified emissions figure without mass).
Response
{"data": {"createGoodsServices": {"success": true, "message": "OK", "rows": 1}}}
Edit, update & delete data #
The same lifecycle rules apply to the API as to SFTP. Every create* mutation is an upsert keyed by externalId (SFTP Unique ID). Send the mutation again to change a row; send quantity fields as zero to remove it. There is no separate delete mutation.
CSV re-upload with the same Unique ID updates a row; Value = 0 deletes it. Full SFTP walkthrough: Edit / update / delete. Field mapping: SFTP and API field alignment.
Update an existing record
To correct data that already reached Klappir:
- Call the same
create*mutation you used originally (createFuel,createWaste, and so on). - Include a record object with the same
externalIdas before. - Change any fields that need updating (
time,classificationKey, quantities, addresses, labels, etc.).
Klappir updates the existing row instead of creating a duplicate. The mutation response can still show success: true — confirm the change in the Klappir platform if needed.
Delete a record
To remove a record, call the same mutation again with the same externalId and set the quantity field(s) you originally used to zero:
| Mutation | Quantity field(s) to set to 0 |
|---|---|
createFuel | The field(s) you populated — liters, kg, m3, and/or kwh |
createCargo | weight and/or distanceTraveled (whichever you sent) |
createWaste | kg |
createFlightTravel | passengers (and/or distance fields if you used them) |
createSurfaceTravel | distanceTraveled and/or passengers |
createHotelTravel | nights |
createGoodsServices | kg and/or kgco2e |
Omitting a record from a new batch does not delete it. Only an explicit zero-quantity resubmit with the same externalId removes the row — the same rule as on SFTP.
Example — update and delete fuel
Acme first submitted diesel for March. They fix the date, then remove a bad row:
// Update — same externalId, corrected time
{
"customerId": "afaf2111-f5cc-4c69-b806-46d318e28b5d",
"externalId": "ACME-FUEL-2024-03-002",
"classificationKey": "15-10-15-05-K001",
"time": "2024-03-02T12:00:00Z",
"liters": 312.0
}
// Delete — same externalId, zero quantity
{
"customerId": "afaf2111-f5cc-4c69-b806-46d318e28b5d",
"externalId": "ACME-FUEL-2024-03-099",
"classificationKey": "15-10-15-05-K001",
"time": "2024-03-15T12:00:00Z",
"liters": 0
}
Recommended correction workflow
| Step | Action |
|---|---|
| 1 | Confirm the record exists in the Klappir platform (wrong totals often mean a failed first submit). |
| 2 | Build a minimal mutation payload — one corrected or zero-quantity record is enough. |
| 3 | POST the mutation; log success, message, and rows from the response. |
| 4 | Verify in the platform. If it still looks wrong, see Troubleshooting common API errors. |
Acme accidentally submitted ACME-FUEL-2024-03-014 twice with different dates. They keep the correct version unchanged and send a second mutation with the stray externalId and liters: 0 to delete the bad copy.
What's next, after your first integration #
Acme's first end-to-end run worked. Now they need to make this a reliable production pipeline — and so will you. A few directions to think about:
Schedule it
Most integrations run on a cron schedule — nightly for the previous day's data. Acme uses a single cron job that submits each day.
Handle errors gracefully
Network errors, validation failures, and transient 5xxs all happen. Retry with backoff. When success: false comes back, log the message field — it usually tells you which record failed and why.
Batch sensibly
Don't submit one record at a time. Most integrations batch by day or by customer. If a batch is too large, split on a natural boundary like customerId.
Pick a stable externalId scheme
Once you've chosen a format, don't change it. Acme uses ACME-FUEL-2024-03-001 — your future self will thank you when reconciling or correcting historical records. To fix or remove rows later, see Edit / update / delete.
Refresh the cache periodically
getClassifications can change as the taxonomy grows. Cache aggressively but refresh on a schedule so new codes show up.
getActiveCustomers and getClassifications can change. Cache aggressively but refresh weekly so new customers and codes show up.
Monitor in the dashboard
Verify data appears in Klappir after submission. Set up alerts in your monitoring stack tied to the rows response — a sudden drop usually means upstream data is missing.
When something goes wrong
For common HTTP and GraphQL payload issues (401, validation errors, customerId, classificationKey), see the Troubleshooting common API errors table in Getting started. If you still need help, email service@klappir.com with your x-api-key identifier (not the key itself), the timestamp of the request, and the response body.
Type reference #
Key types referenced throughout the API. Standard GraphQL scalars (String, Boolean, Int, Float, ID) follow standard definitions. Tables below use the same Req column as mutation inputs: required vs optional.
Customer
| Req | Field | Type | Description |
|---|---|---|---|
| required | id | ID! | UUID — use as customerId in all mutations. Direct customers: same value as Settings → System → Entity IDs in the Klappir Platform. |
| required | name | String! | Company display name |
| required | registration | String! | Company registration number |
| optional | description | String | Optional description |
| optional | address | CustomerAddress | Physical address fields |
CreateDataPayload
Returned by most create* mutations.
| Req | Field | Type | Description |
|---|---|---|---|
| required | success | Boolean! | Whether the operation succeeded |
| optional | message | String | Human-readable status or error message |
| optional | rows | Int | Number of records accepted |
Classification
One row from getClassifications.
| Req | Field | Type | Description |
|---|---|---|---|
| required | key | ID! | Use as classificationKey / waste keys on mutations |
| required | level | Int! | Hierarchy depth |
| required | dataType | DataTypeEnum! | Target data category |
| required | usage | String! | Internal usage label |
| required | description | String! | Display label |
GetClassificationsInput
| Req | Field | Type | Description |
|---|---|---|---|
| optional | keyPrefix | String | Return keys starting with this prefix |
| optional | dataType | [DataTypeEnum!] | Filter to one or more data types — e.g. ["FUEL", "WASTE"] |
CreateWastePayload
Returned specifically by createWaste.
| Req | Field | Type | Description |
|---|---|---|---|
| required | status | StatusEnum! | SUCCESS or FAILURE |
| required | rows | Int! | Number of records accepted |
DataTypeEnum
| Value | Description |
|---|---|
FUEL | Liquid and gaseous fuels — createFuel, Fuel template |
WASTE | Waste disposal — createWaste, Waste template |
FLIGHT | Air travel — createFlightTravel, Flight template |
SURFACE_TRAVEL | Road, rail, or sea travel — createSurfaceTravel, Surface template |
HOTEL | Hotel stays — createHotelTravel, Hotel template |
CARGO | Upstream transportation & distribution — createCargo, Upstream template |
GOODS_SERVICES | Purchased goods and services — createGoodsServices, Goods & Services template |
CargoTransportTypeEnum (upstream transportation & distribution)
| Value | Description |
|---|---|
INBOUND | Freight arriving at customer's location |
OUTBOUND | Freight leaving customer's location |
INTERNAL | Internal logistics between sites |
AddressInput
Nested object for structured locations on fuel, upstream transportation & distribution, hotel, surface travel, and waste collection (pickup) payloads. country is an ISO 3166-1 alpha-2 code; other members are optional text or coordinates.
| Req | Field | Type | Description |
|---|---|---|---|
| required | country | CountryIsoLocode! | ISO country code (e.g. IS) |
| optional | address | String | Street line |
| optional | city | String | City |
| optional | postalCode | String | Postal or ZIP code |
| optional | municipality | String | Municipality or district |
| optional | label | String | Short site label (yard, pier, building) |
DateTime
ISO 8601 datetime string. Example: "2024-03-01T08:00:00.000Z". Always use UTC (suffix Z).