# API key
Source: https://docs.variable.global/api-reference/auth/token/api-key
Use an API key to authenticate your requests
You can find API keys in the [Users](https://app.variable.global/users) section.
API access is a paid feature. If you would like to add API access to your account, contact us.
Only Admins can create/update API keys and permissions.
# Get Token
Source: https://docs.variable.global/api-reference/auth/token/post
POST /auth/token
Get an access token to authenticate your requests
This is only required if you're **not** using an [API Key](/api-reference/auth/token/api-key).
# List Accounts
Source: https://docs.variable.global/api-reference/v1/account/get
GET /v1/account
List the accounts the authenticated user can access.
This endpoint is only available with OAuth or session authentication. Scoped API keys
(`api_xxx`) are bound to a single account, so this route rejects them with a 401.
# Delete Activity Group
Source: https://docs.variable.global/api-reference/v1/activity-group/delete
DELETE /v1/activity/group/{uuid}
Delete an activity group
Requires permission:
# Get Activity Group
Source: https://docs.variable.global/api-reference/v1/activity-group/get
GET /v1/activity/group/{uuid}
Get an activity group
Requires permission:
# List Activity Groups
Source: https://docs.variable.global/api-reference/v1/activity-group/list
GET /v1/activity/group
List your company's activity groups
Requires permission:
# Update Activity Group
Source: https://docs.variable.global/api-reference/v1/activity-group/patch
PATCH /v1/activity/group/{uuid}
Rename an activity group
Requires permission:
# Create Activity Group
Source: https://docs.variable.global/api-reference/v1/activity-group/post
POST /v1/activity/group
Create an activity group
Requires permission:
# Bulk Activity Creation
Source: https://docs.variable.global/api-reference/v1/activity/bulk
POST /v1/activity/bulk
Create/update activities in bulk
Requires permission:
Requires permission:
Read more about the [Activity](/docs/activity) data models.
Bulk operations take a maximum of 1,000 items per request.
# Bulk Activity Deletion
Source: https://docs.variable.global/api-reference/v1/activity/bulk-delete
POST /v1/activity/bulk/delete
Delete activities in bulk
Requires permission:
Read more about the [Activity](/docs/activity) data models.
Bulk operations take a maximum of 1,000 items per request.
# Delete Activity
Source: https://docs.variable.global/api-reference/v1/activity/delete
DELETE /v1/activity/{uuid}
Delete an activity
Requires permission:
# Get Activity
Source: https://docs.variable.global/api-reference/v1/activity/get
GET /v1/activity/{uuid}
Get an activity
Requires permission:
Read more about the [Activity](/docs/activity) data models.
# List Activities
Source: https://docs.variable.global/api-reference/v1/activity/list
GET /v1/activity
Get a list of activities
Requires permission **activity:report** to get high level data.
Requires permission **activity:read** to get detailed data.
Read more about the [Activity](/docs/activity) data models.
# Update Activity
Source: https://docs.variable.global/api-reference/v1/activity/patch
PATCH /v1/activity/{uuid}
Update an activity
Requires permission:
Read more about the [Activity](/docs/activity) data models.
# Create Activity
Source: https://docs.variable.global/api-reference/v1/activity/post
POST /v1/activity
Create a new activity
Requires permission:
Read more about the [Activity](/docs/activity) data models.
# Delete Dataset
Source: https://docs.variable.global/api-reference/v1/dataset/delete
DELETE /v1/dataset/{uuid}
Delete a dataset from your account
Requires permission:
If the dataset was created in your account, it is permanently deleted. If it was added from the Variable Database or shared by a supplier, it is removed from your account but the original remains available in the database.
Datasets that are in use cannot be deleted. If the dataset is referenced by a switch, mix, product, or transport type, the request fails with `400` and the body lists the affected counts (for example, `switches: 2, products: 1`). Unlink those references first, then retry the delete.
# Get Dataset
Source: https://docs.variable.global/api-reference/v1/dataset/get
GET /v1/dataset/{uuid}
Get a dataset by UUID
Requires permission:
Read more about the [Dataset](/docs/dataset) data model.
# Dataset Image
Source: https://docs.variable.global/api-reference/v1/dataset/image
PATCH /v1/dataset/{uuid}/image
Update a dataset image
Requires permission:
# Set Dataset Impacts
Source: https://docs.variable.global/api-reference/v1/dataset/impacts/put
PUT /v1/dataset/{uuid}/impacts
Set a draft dataset's impact indicators, keyed by EN 15804+A2 code
Requires permission:
Impacts are keyed by canonical EN 15804+A2 indicator code — the same vocabulary the read endpoints
return. Send only life-cycle stage values (`A1`–`A5`, `B1`–`B7`, `C1`–`C4`, `D`); totals are computed
server-side. The dataset must be in `draft` state, and indicators you omit are left unchanged.
Each indicator is stored under the method the dataset's document type implies: on a dataset set to
**Other** every indicator you send is stored as a plain figure, on an **EPD** dataset as its EN 15804+A2
indicator.
Set the document type with `staticImpactMethod` — `en15804_a2` (EPD) or `unknown` (any other source) —
when you create the dataset, or later with `PATCH /v1/dataset/{uuid}`. It is returned on every dataset
read, so you can tell which datasets declare one. A dataset that declares none stores its impacts under
the EN 15804+A2 method and stays undeclared; declaring it afterwards does not move values already
written, so set it before writing impacts if you know what the source is.
Accounts without all impact indicators enabled may only write the canonical GWP-fossil indicator;
sending any other indicator returns a 403 and saves nothing. Contact Variable to enable the full
indicator set.
# List Datasets
Source: https://docs.variable.global/api-reference/v1/dataset/list/get
GET /v1/dataset
Get a list of datasets in your account
Requires permission:
Read more about the [Dataset](/docs/dataset) data model.
# Update Dataset
Source: https://docs.variable.global/api-reference/v1/dataset/patch
PATCH /v1/dataset/{uuid}
Update a dataset
Requires permission:
# Create Dataset
Source: https://docs.variable.global/api-reference/v1/dataset/post
POST /v1/dataset
Create a new dataset
Requires permission:
# Delete Energy
Source: https://docs.variable.global/api-reference/v1/energy/delete
DELETE /v1/energy/{uuid}
Delete an energy element
Requires permission:
# Get Energy
Source: https://docs.variable.global/api-reference/v1/energy/get
GET /v1/energy/{uuid}
Get an energy element by UUID
Requires permission:
# Energy Image
Source: https://docs.variable.global/api-reference/v1/energy/image
PATCH /v1/energy/{uuid}/image
Update an energy element image
Requires permission:
# List Energy
Source: https://docs.variable.global/api-reference/v1/energy/list/get
GET /v1/energy
Get a list of energy elements
Requires permission:
# Update Energy
Source: https://docs.variable.global/api-reference/v1/energy/patch
PATCH /v1/energy/{uuid}
Update an energy element
Requires permission:
# Create Energy
Source: https://docs.variable.global/api-reference/v1/energy/post
POST /v1/energy
Create a new energy element
Requires permission:
# Geocode an Address
Source: https://docs.variable.global/api-reference/v1/geolocation/get
GET /v1/geolocation
Geocode a free-text address into ranked, un-persisted candidates.
Requires permission:
# Persist a Geolocation
Source: https://docs.variable.global/api-reference/v1/geolocation/post
POST /v1/geolocation
Persist a chosen candidate (or a manual point) and get back its uuid.
Requires permission:
# Delete Location
Source: https://docs.variable.global/api-reference/v1/location/delete
DELETE /v1/location/{uuid}
Delete a location
Requires permission:
# Get Location
Source: https://docs.variable.global/api-reference/v1/location/get
GET /v1/location/{uuid}
Get a location
Requires permission:
# List Locations
Source: https://docs.variable.global/api-reference/v1/location/list
GET /v1/location
List the locations your company owns
Requires permission:
# Update Location
Source: https://docs.variable.global/api-reference/v1/location/patch
PATCH /v1/location/{uuid}
Update a location
Requires permission:
# Create Location
Source: https://docs.variable.global/api-reference/v1/location/post
POST /v1/location
Create a new location
Requires permission:
# Delete Material
Source: https://docs.variable.global/api-reference/v1/material/delete
DELETE /v1/material/{uuid}
Delete a material
Requires permission:
# Get Material
Source: https://docs.variable.global/api-reference/v1/material/get
GET /v1/material/{uuid}
Get a material by UUID
Requires permission:
Read more about the [Material](/docs/material) data model.
# Material Image
Source: https://docs.variable.global/api-reference/v1/material/image
PATCH /v1/material/{uuid}/image
Update a material image
Requires permission:
# List Materials
Source: https://docs.variable.global/api-reference/v1/material/list/get
GET /v1/material
Get a list of materials
Requires permission:
Read more about the [Material](/docs/material) data model.
# Update Material
Source: https://docs.variable.global/api-reference/v1/material/patch
PATCH /v1/material/{uuid}
Update a material
Requires permission:
# Create Material
Source: https://docs.variable.global/api-reference/v1/material/post
POST /v1/material
Create a new material
Requires permission:
# Delete Input
Source: https://docs.variable.global/api-reference/v1/model/inputs/delete
DELETE /v1/model/{modelId}/input/{inputId}
Delete model input
Requires permission:
# List Inputs
Source: https://docs.variable.global/api-reference/v1/model/inputs/get
GET /v1/model/{uuid}/input
Get a list of model inputs
Requires permission:
Read more about the [Model & Input](/docs/model) data models.
# Edit Input
Source: https://docs.variable.global/api-reference/v1/model/inputs/patch
PATCH /v1/model/{modelId}/input/{inputId}
Edit model input
Requires permission:
# Create Input
Source: https://docs.variable.global/api-reference/v1/model/inputs/post
POST /v1/model/{uuid}/input
Create a new model input for the specified model
Requires permission:
# List Orgs
Source: https://docs.variable.global/api-reference/v1/org/get
GET /v1/org
Get a list of company Orgs.
Requires permission:
# Delete Process
Source: https://docs.variable.global/api-reference/v1/process/delete
DELETE /v1/process/{uuid}
Delete a process element
Requires permission:
# Get Process
Source: https://docs.variable.global/api-reference/v1/process/get
GET /v1/process/{uuid}
Get a process element by UUID
Requires permission:
# Process Image
Source: https://docs.variable.global/api-reference/v1/process/image
PATCH /v1/process/{uuid}/image
Update a process element image
Requires permission:
# List Processes
Source: https://docs.variable.global/api-reference/v1/process/list/get
GET /v1/process
Get a list of process elements
Requires permission:
# Update Process
Source: https://docs.variable.global/api-reference/v1/process/patch
PATCH /v1/process/{uuid}
Update a process element
Requires permission:
# Create Process
Source: https://docs.variable.global/api-reference/v1/process/post
POST /v1/process
Create a new process element
Requires permission:
# Delete Product
Source: https://docs.variable.global/api-reference/v1/product/delete
DELETE /v1/product/{uuid}
Delete a product
Requires permission:
# Get Product
Source: https://docs.variable.global/api-reference/v1/product/get
GET /v1/product/{uuid}
Get a product by UUID
Requires permission:
Read more about the [Product](/docs/material) data model.
# Product Image
Source: https://docs.variable.global/api-reference/v1/product/image
PATCH /v1/product/{uuid}/image
Update a product image
Requires permission:
# List Products
Source: https://docs.variable.global/api-reference/v1/product/list/get
GET /v1/product
Get a list of products
Requires permission:
Read more about the [Product](/docs/material) data model.
# Edit Product
Source: https://docs.variable.global/api-reference/v1/product/patch
PATCH /v1/product/{uuid}
Edit a product
Requires permission:
# Create Product
Source: https://docs.variable.global/api-reference/v1/product/post
POST /v1/product
Create a product
Requires permission:
# List Scopes
Source: https://docs.variable.global/api-reference/v1/scope/get
GET /v1/scope
Get a list of Scopes.
Requires permission:
# Search
Source: https://docs.variable.global/api-reference/v1/search/list
GET /v1/search
Search the Variable Database
# Delete Supplier
Source: https://docs.variable.global/api-reference/v1/supplier/delete
DELETE /v1/supplier/{uuid}
Delete a supplier
Requires permission:
# Get Supplier
Source: https://docs.variable.global/api-reference/v1/supplier/get
GET /v1/supplier/{uuid}
Get a supplier by UUID, syncId, or internalId
Requires permission:
# Supplier Image
Source: https://docs.variable.global/api-reference/v1/supplier/image
PATCH /v1/supplier/{uuid}/image
Update a supplier image
Requires permission:
# List Suppliers
Source: https://docs.variable.global/api-reference/v1/supplier/list/get
GET /v1/supplier
Get a list of suppliers
Requires permission:
# Edit Supplier
Source: https://docs.variable.global/api-reference/v1/supplier/patch
PATCH /v1/supplier/{uuid}
Edit a supplier
Requires permission:
The request edits the supplier named in the URL; a `uuid` in the body is ignored. You can edit a supplier your
account created until that supplier claims its own Variable account. After that, or for a supplier another account
created, the request returns a 403 and saves nothing.
# Create Supplier
Source: https://docs.variable.global/api-reference/v1/supplier/post
POST /v1/supplier
Create a supplier
Requires permission:
# Add Source
Source: https://docs.variable.global/api-reference/v1/switch/add-source
POST /v1/switch/{switchId}/source/{sourceId}
Add source to Switch
Requires permission:
# Delete Switch
Source: https://docs.variable.global/api-reference/v1/switch/delete
DELETE /v1/switch/{uuid}
Delete a switch
Requires permission:
# Get Switch
Source: https://docs.variable.global/api-reference/v1/switch/get
GET /v1/switch/{uuid}
Get a switch by UUID
Requires permission:
Read more about the [Switch](/docs/switch) data model.
# Switch Image
Source: https://docs.variable.global/api-reference/v1/switch/image
PATCH /v1/switch/{uuid}/image
Update a switch image
Requires permission:
# List Switches
Source: https://docs.variable.global/api-reference/v1/switch/list
GET /v1/switch
Get a list of Switches
Requires permission:
Read more about the [Switch](/docs/switch) data model.
# Edit Switch
Source: https://docs.variable.global/api-reference/v1/switch/patch
PATCH /v1/switch/{uuid}
Edit a switch
Requires permission:
# Create Switch
Source: https://docs.variable.global/api-reference/v1/switch/post
POST /v1/switch
Create a switch
Requires permission:
# Remove Source
Source: https://docs.variable.global/api-reference/v1/switch/remove-source
DELETE /v1/switch/{switchId}/source/{sourceId}
Remove source from Switch
Requires permission:
# List Taxonomies
Source: https://docs.variable.global/api-reference/v1/taxonomy/get
GET /v1/taxonomy
Get a list of Taxonomies.
Requires permission:
# Delete Transport Lane
Source: https://docs.variable.global/api-reference/v1/transport/lane/delete
DELETE /v1/transport/lane/{uuid}
Delete a transport lane
Requires permission:
# Get Transport Lane
Source: https://docs.variable.global/api-reference/v1/transport/lane/get
GET /v1/transport/lane/{uuid}
Get a transport lane by UUID or syncId
Requires permission:
# List Transport Lanes
Source: https://docs.variable.global/api-reference/v1/transport/lane/list/get
GET /v1/transport/lane
Get a list of transport lanes
Requires permission:
# Update Transport Lane
Source: https://docs.variable.global/api-reference/v1/transport/lane/patch
PATCH /v1/transport/lane/{uuid}
Update a transport lane and its legs
Requires permission:
# Create Transport Lane
Source: https://docs.variable.global/api-reference/v1/transport/lane/post
POST /v1/transport/lane
Create a transport lane with its legs inline
Requires permission:
# Delete Transport Mode
Source: https://docs.variable.global/api-reference/v1/transport/mode/delete
DELETE /v1/transport/mode/{code}
Delete a custom transport mode
Requires permission:
# Get Transport Mode
Source: https://docs.variable.global/api-reference/v1/transport/mode/get
GET /v1/transport/mode/{code}
Get a transport mode by code or UUID
Requires permission:
# List Transport Modes
Source: https://docs.variable.global/api-reference/v1/transport/mode/list/get
GET /v1/transport/mode
Get all built-in modes plus your custom modes
Requires permission:
# Update Transport Mode
Source: https://docs.variable.global/api-reference/v1/transport/mode/patch
PATCH /v1/transport/mode/{code}
Update a custom transport mode
Requires permission:
# Create Transport Mode
Source: https://docs.variable.global/api-reference/v1/transport/mode/post
POST /v1/transport/mode
Create a custom transport mode
Requires permission:
# List Units
Source: https://docs.variable.global/api-reference/v1/unit/get
GET /v1/unit
Unit information.
# REST API
Source: https://docs.variable.global/api-reference/welcome
The Variable REST API has endpoints for managing Products, Materials, Models and Activities.
Try out the API in
[Postman](https://www.postman.com/variable-global/variable-api/overview)
## Pre-requisites
You need an API User with an API Key. Account admins can manage API Users in [users](https://app.variable.global/users).
### Rate limit
To ensure the best performance for all users, we have a rate limit of **10 requests per second**.
# 2022
Source: https://docs.variable.global/changelog/2022
## Category models
Category models define how product categories map to emission factor categories, enabling
more accurate and automated impact calculations.
## Transport calculator
A dedicated transport calculator computes emissions from logistics based on distance, mode,
and cargo weight.
## Public database
A public database of emission factors is now available for all users to search and reference
when building product footprints.
## Use stage visualization
Life-cycle stage visualization shows how environmental impacts are distributed across upstream,
direct, and downstream stages.
## Document management
Documents can now be uploaded and attached to products for supporting evidence and certifications.
## Company subscription management
Subscription management has been added with plan selection and billing integration.
## Full-text search
Product and activity search now uses Lucene-based full-text indexing for faster, more
relevant results.
## Data import
CSV data import has been introduced with support for multiple activity formats, chunked
processing for large files, and email notifications on completion.
## Dashboard
The main dashboard has been redesigned with date filtering (MTD, YTD), home page filtering,
and improved data presentation.
## Share footprint
Product footprints can now be shared externally via dedicated sharing links.
## GHG report
Emission sources can now be included in GHG Protocol reports with scope categorization.
## Data requests
Organizations can now request environmental data from their suppliers, with status tracking
and completion workflows.
## Product visibility levels
Products support configurable visibility levels (private, shared, public) for controlling
access.
## Breadcrumb navigation
Breadcrumb navigation has been added for easier wayfinding within the application.
## Passport UI
A product passport view provides a public-facing summary of a product's environmental data.
## Reduce page
A dedicated reduce page helps identify opportunities for lowering environmental impact.
## Taxonomy UI
A visual taxonomy browser allows navigating and assigning product classifications.
## Company admin
Company administration features include member management, settings, and profile
configuration.
## Platform launch
The Variable platform has launched with core capabilities for product carbon footprint
modeling, activity tracking, supplier management, and environmental data search.
# 2023
Source: https://docs.variable.global/changelog/2023
## PACT API
The PACT (Partnership for Carbon Transparency) Pathfinder API has been integrated, enabling
standardized carbon footprint data exchange between organizations. Includes a dedicated UI for
managing PACT connections and syncing footprint data.
## Life-cycle bar
A new life-cycle bar visualization shows the environmental impact breakdown across life-cycle stages
at a glance.
## Auto-categorize activities
Activities can now be automatically categorized based on their properties, reducing manual
classification work.
## Premium emission factors
Premium emission factor data is now available to paid users.
## Saved filters
Search filters can now be saved and associated with a company for quick access to frequently used
filter combinations. Command palette (Cmd+K) also supports filtering.
## Activity states
Activities now support state management (draft, active, archived) with proper validation and
state transition rules.
## Data import improvements
The data import page has been redesigned with better status tracking and user experience.
## Processing inputs
A new processing input type allows modeling manufacturing processes with dedicated unit types
and flow editing.
## Editable flow
The product flow diagram now supports inline editing, making it easier to modify product
compositions directly from the visual view.
## Product duplication
Products can now be duplicated including their attached transport and processing inputs.
## Batches
Batch tracking has been introduced for grouping activities by production run, with dedicated
tabs, CO2e calculations, and relative intensity metrics. Preview
## Role-based access control
RBAC has been implemented with organization-level permissions, company member management, and
role assignment capabilities.
## Product search improvements
Search has been enhanced with better result ranking and improved product preview modals.
## Bulk activity updates
Activities can now be updated in bulk for efficient data management.
## Organization management
Full organization (Org) management has been introduced, including editing, flow visualization,
member management, and the ability to create new companies within an organization.
## Notifications
In-app notifications have been added for important events and updates.
## Dark mode
Dark mode theme support has been added with a light/dark toggle.
## Footprint flow
A new visual flow diagram shows how product footprints are composed from their inputs, with
drag-and-drop support for slide-in panels. Preview
## Report by category
Environmental reports can now be broken down by product category for more granular analysis.
## Dashboard updates
The main dashboard has been redesigned with better data presentation and navigation.
## Calculators
Emission calculators have been introduced, allowing users to compute environmental impacts using
predefined formulas and input fields.
## Product archiving
Products and activities can now be archived to keep the workspace clean while preserving
historical data.
## Search improvements
Search now supports tag-based filtering, typeahead suggestions, and improved result presentation.
## Activity import
Activities can now be imported from CSV files with automatic field mapping and validation.
## Supplier connection
Organizations can now establish connections with their suppliers for data exchange.
## Onboarding
A guided onboarding flow helps new users set up their organization and get started with
their first product footprint.
# 2024
Source: https://docs.variable.global/changelog/2024
## API image endpoints
New API endpoints for uploading and managing images on products, materials, suppliers, and switches.
## BOM import
A new bill of materials (BOM) import feature allows uploading structured BOM data from CSV files to create
product hierarchies.
## CO2e display improvements
CO2e values are now displayed with better formatting, handling very small numbers and using consistent precision.
## Supplier API
New public API endpoints for managing suppliers - create, read, update, and delete supplier records.
## API token management
API tokens can now be created and managed directly from the UI, with configurable permissions.
## Viewer role
A new read-only user role, Viewer, allows stakeholders to view data without edit permissions.
## Processing calculator updates
Processing calculators now support any unit type, giving more flexibility in how manufacturing processes are
modeled.
## Materials import
Materials can now be imported in bulk from CSV files with automatic field mapping.
## Supplier archive
Suppliers can now be archived, removing them from active views while preserving their data.
## Transition plan timeline
A new timeline view for transition plans allows tracking sustainability goals and milestones over time.
## Public API launch
The Variable Public API has been launched, providing RESTful endpoints for programmatic access to products,
materials, and environmental data.
## Weight tracking
Product weight tracking has been introduced, supporting mass-based calculations and unit conversions.
## Electricity modeling
Full electricity modeling support has been added, including electricity activities, location-based emission
factors, and renewable percentage tracking. Electricity is now a first-class element type in the LCA model.
Preview
## Report export
LCA reports can now be exported for sharing and archival purposes. Preview
## Request footprint
Users can now request carbon footprint data from suppliers directly through the platform. Preview
## Activity query improvements
Activity queries have been significantly optimized for better performance with large datasets.
## Global UsedIn
The "Used In" view is now available globally, showing where any material or product is referenced across the
entire organization.
## Transport UI and locations
Transport modeling has been updated with improved UI, named transport lanes, geolocation search with
previously used locations, and sea route matching.
## PACT API conformance
The PACT (Partnership for Carbon Transparency) API implementation has been updated to meet conformance
requirements, including proper unit handling and sector-specific rules.
## Transport element
Transport is now a first-class element type in the LCA model, with dedicated distance entry, mode
matching, and emission factor support.
## Footprint export
Product carbon footprints can now be exported for sharing with external stakeholders. Preview
## Digital product passport
Initial support for digital product passport (DPP) data structures and display.
## Data visualization
A new data visualization module provides charts and visual breakdowns of environmental impact data, with
privacy controls for sharing.
## CPC codes
Central Product Classification (CPC) codes can now be assigned to products, improving alignment with
international classification standards.
## Activity date handling
Activity date change handling has been improved with better validation and edge case support.
# 2025
Source: https://docs.variable.global/changelog/2025
## Impact data extraction from PDF
Upload a PDF containing environmental impact data and the system will automatically extract and map impact values
to the correct indicators. Preview
## Product and material versioning
Products and materials now support versioning. Create new versions, track version history, and manage
latest/draft states. Preview
## Dataset impact object support
Datasets now support viewing and editing the full impact object, not just GWP values.
## LCA modelling sections
The modelling screen now organizes inputs into collapsible LCA stage sections (A1-A3, A4, C1-C4, D), making
complex models easier to navigate.
## GWP-GHG/IOBC from elementary exchange data
Impact calculations now support GWP-GHG/IOBC (instantaneous oxidation of biogenic carbon) indicators derived from
elementary exchange data in ecoinvent datasets.
## Export product as JSON
Products can now be exported as JSON files, including the full model structure. This also serves as a model
backup format. Preview
## Modeled weight tracking
The modeled weight is now saved to the product, enabling weight mismatch warnings when the declared weight
differs from the sum of material inputs. Preview
## Activity grouping
Activities can now be organized into groups for better categorization and reporting.
## EPD Variant Importer
A new importer enables creating product variants from EPD data, supporting weight and non-amount values.
Preview
## Product Category Rule support
Product category rules (PCRs) can now be associated with products, enabling PCR-specific impact calculations and
reporting.
## Weight in product and material import
The data importer now supports weight columns for products and materials, automatically mapping imported weights
to the correct unit.
## Bulk inventory updates
Bulk operations have been extended to support inventory-level updates, including taxonomy and dataset assignment across
multiple items. Preview
## Impact tables
A new impact tables view provides a comprehensive breakdown of environmental impacts across all indicators
and life-cycle stages.
## Public API: `syncId` support
A new `syncId` property has been added across all entity types, enabling external systems to maintain stable
references to Variable entities across syncs and imports. Preview
## Public API: Activity endpoints
The public API now includes full CRUD endpoints for activities, including bulk operations.
## Inventory table redesign
The inventory table has been redesigned with improved sorting, filtering, and a cleaner layout. Inputs are now
organized by LCA stage with expandable sections.
## Search by short code
Products and materials can now be found by searching their short code identifiers.
## Import modernization
The data import system has been modernized with better column detection, date handling, and error reporting.
## Impact categories
The impact system now supports the full set of EN 15804+A2 impact categories, moving beyond GWP-only
calculations.
## Deep BOM search
Search now traverses the full bill of materials hierarchy, finding components at any nesting depth.
## VAR-ID
A new human-readable identifier (`varId`) has been introduced for all entities, providing a shorter
alternative to UUIDs for display and reference. Preview
# 2026
Source: https://docs.variable.global/changelog/2026
## Map any activities to an element in bulk
The **Element** action in the activity bulk toolbar no longer needs the list
filtered to a supplier. It searches your own elements, or that supplier's
products when the list is filtered to one, and a new **Show activities with no
element** shortcut finds the rows still missing one. See
[Mapping Activities to an Element in bulk](/docs/help/bulk-map-activities).
## Activity records reach MCP callers
The MCP `list_activities` tool now returns activity records, and
`get_activity_summary` returns them when it's called without `list`; both
returned totals only before. A `GET /v1/activity` request with `list=` no
longer carries `data`, `page` or `pageSize`, since its records were always
empty.
## Assign an activity group through the API and MCP
`POST /v1/activity`, `PATCH /v1/activity/:activityId` and
`POST /v1/activity/bulk` accept `group: { uuid }`, and `group: null` clears it.
A group that isn't one of your company's is now rejected with a 400; before, the
write succeeded and dropped the activity's existing group. The MCP server adds
`list_activity_groups`, `create_activity_group`, `update_activity_group` and
`delete_activity_group`, and `create_activity` and `update_activity` take
`group`.
## A new group version carries its LCAs and rules
A new draft version of a finalized LCA group now brings its member LCAs with it.
Locked LCAs stay in the earlier version too, while Draft LCAs move to the new
one. Each rule becomes the next draft of itself rather than a fresh copy.
Feature flag
## Activity CO2e by scope, org, taxonomy and group
`GET /v1/activity` with `list=` now gives every scope, org, taxonomy and group
entry the CO2e and count of the activities attached directly to it, and accepts
`list=groups`. Org entries carry their parent so you can roll them up. The MCP
`get_activity_summary` tool offers groups too.
## Activity groups in the public API
New endpoints list, read, create, update and delete activity groups under
`/v1/activity/group`.
## An LCA joins a group only within its inventory
Adding an LCA to an LCA group now checks that the group's inventory already
holds every source the LCA uses, and names any that are missing. From the LCAs
tab, Variable offers to add those sources to the inventory first and links the
LCA once they're in. A version past draft — locked, in review, rejected or
verified — still takes and releases LCAs, but a missing source there means
creating a new draft and adding the LCA to that. An LCA can sit in several
versions of one group, never in two groups.
## Origin names a region when no country is known
A material that nothing places in a country now shows the region it's placed in
— Europe, Scandinavia or North America, for example — in the **Origin** column
and in the tables sent to SmartEPD. Countries are listed first, then regions.
World and Global still add nothing.
## Materials show where they come from
The Materials and Packaging materials tables on a Declaration's report have a
new **Origin** column listing the countries each row's materials are made in,
read from the material's facilities, its location, or its supplier's location.
The SmartEPD sync sends the same countries in its material composition and
packaging tables. A Declaration already past draft keeps its tables as they
were.
## Transport coverage and modeled vs declared measure what resolved
Transport coverage and the modeled vs declared comparison now sum the lines
that do have a weight instead of reading `N/A` over the whole control, and
leave out a leg or a half only where nothing in it resolved. Verdicts on
existing models can change.
Feature flag
## End-of-life and full life-cycle measure what resolved
The end-of-life and full life-cycle mass balance controls now sum the lines
that do have a weight instead of reading `N/A` over the whole control, and
report `N/A` only where a figure they state resolved nothing. Verdicts on
existing models can change.
Feature flag
## Set your company's mass balance tolerance
An Owner or Admin sets the tolerance every mass balance control is judged at,
from 0% to 10%, under **Settings → Company**. Clear the field to go back to the
5% default. The tolerance that counts is the one set by the company that owns
the product, never the reader's, so a customer opening your product sees the
verdict your tolerance gives — and every control states the tolerance it was
judged at, in the modal's legend and in the exported table.
Feature flag
## The mass balance tolerance defaults to 5%
A company that has set no tolerance of its own is now judged at ±5% rather than
±1%. Balances checked before this read as out of date and re-check the next time
the model is read, so a control that was failing narrowly may now pass. A
finalized version keeps the 1% it was judged at.
Feature flag
## `list_activities` returns a compact record set
The MCP `list_activities` tool now projects a default field set — `uuid`,
`syncId`, `description`, the dates, `quantity`, `unit`, `CO2e`, `scope`, `org`,
`taxonomy` and `group` — instead of the full activity record, so a page of
results costs a fraction of the context it did. Name the fields you want in
`return`, or pass `return=all` to get the whole record back.
## Mass balance controls measure what resolved
Production balance and packaging now sum the lines that do have a weight
instead of reading `N/A` over the whole control, and report `N/A` only where a
figure they state resolved nothing at all — naming that cell rather than the
first incomplete one. A control that fails while one of the cells behind the
gap is missing a weight now reports amber rather than red, since the missing
weight may be what caused the deviation.
Feature flag
## `list_activities` filters by date, org, scope, taxonomy and group
The MCP activity read tools now declare the filters v1 already applies, so they
actually arrive: `org`, `scope`, `taxonomy`, `activityGroup` and `depth`.
`startDate` / `endDate` were being read as a year number that only product
queries use, so a date range returned account-wide results; they now narrow the
range as intended. An `org` or `taxonomy` filter includes its children unless
`depth` is 0.
## Oversized list responses keep whole records
When using the MCP a list response over the 25,000-character limit was cut at the
character, which usually landed mid-record and returned text that was no longer
valid JSON. It now keeps as many whole records as fit and adds a `truncated` block
naming how many were returned and how many were left out; `count`, `page` and the
totals keep describing the full result.
## Bulk element mapping refuses a unit it can't convert
Mapping an element onto activity rows multiplied the quantities without
checking that they measure the same kind of thing, so pointing a kg-declared
element at rows metered in litres produced a plausible-looking wrong number.
A row whose quantity can't be expressed in the element's declared unit is now
left out of the write entirely. A litre row still maps where the element states
the volume one kilogram occupies.
## A mass balance control names its lines without a weight
The lines a control couldn't weigh are now named under a pass or a fail, not
only under `N/A`, in the modal and in the exported table. The card reads "N
lines without a weight" so it stays distinct from the lines a failure blames.
Feature flag
## Modeled vs declared is a warning, not an error
A declared weight often comes from a customer's own system and may not be the
real weight, so a model that disagrees with it is now flagged amber rather than
red. The comparison and the tolerance are unchanged — only the severity — so a
product that read as failing on this control alone now reads as a warning.
Feature flag
## Filter the model by version state
The model view takes a filter on the version state of the element behind
each input, so you can pull up just the draft lines holding a verification up.
It hides rows at every nesting level and never moves a figure: totals and shares
keep reading the unfiltered model. A line backed by the Variable Database has no
version state of its own.
Feature flag
## Production balance weighs the model against itself
The A1 control stopped reading the declared product weight: it now weighs the
ancillaries you model at A1 and A3 against the waste the model states at A3,
with the product on both sides. It declines rather than passing where the model
states neither an ancillary nor an A3 waste line, and it blames only the cells
that can actually be wrong. End-of-life nets the A1 ancillaries out of the A3
scrap too, so scrap stated as an A1 ancillary is no longer subtracted twice.
Verdicts on existing models can change either way.
Feature flag
## Un-verifying steps back the versions that rest on the sign-off
Revoking a sign-off now returns the verified versions that use that element to
**Locked** as well, up through the products and LCA groups that consume it — a
sign-off given on a model is only as good as the elements underneath it. The
cascade runs from the bulk **Change state** action too. Re-verify from the
changed element upward once each part of the model has been looked at again.
Feature flag
## Bulk sign-off names the rows it couldn't verify
**Mark as verified** in the bulk toolbar now applies the same model check the
single-item action does. A row whose model still uses unverified elements is
reported as **blocked** — counted separately from skipped and not found — and
a **Cannot verify** panel names each unverified element, where it sits in the
model, and which selected rows it holds up. Verify those first, then retry the
blocked rows. Feature flag
## Verify checks the model before it lets you sign off
**Mark as verified** is now disabled until the elements the model uses are
verified themselves, and the dialog lists what's blocking it — each unverified
element your company owns, its version state, and where it sits in the model.
Elements owned by the Variable database or another company don't need your
sign-off. Work from the bottom of the model upward, then return to the parent.
Feature flag
## Primary data share on dataset-backed elements
An element backed directly by a dataset — a dataset container or an electricity
element — has no inputs to average, so it recorded a primary data share of `0`
and every model above it inherited that zero. It now inherits the share from
the dataset behind it, and a share you've declared yourself still wins. Shares
on models you already have move on their next recalculation.
## Public API: the taxonomy path filter matches whole segments
`GET /v1/taxonomy?path=` compared the path as a plain prefix, so `path=material`
also returned `material-handling` and its descendants. It now matches on segment
boundaries, and a trailing slash is trimmed rather than matching nothing. A
query that relied on the old prefix behavior returns fewer rows.
## Rules can target the quantity a line states
A rule can now measure the quantity typed on an input line rather than what
that line weighs, so a process line's kilowatt-hours or a count of fasteners
is targetable. Pick **Line quantity** in the parameter picker and it branches
into one row per dimension the group's own lines are metered in; a matched
line metered in another dimension is named as a gap, and a weight rule whose
matched lines mix product mass with a process line's kilograms reads as not
applicable rather than summing two different things.
## Saved views on the locations list
The locations list now runs on the shared data table, so it has quick search,
a filter bar, a column picker, row height, a sticky header and saved views —
keep a view to yourself or share it with your company. The address bar carries
whatever you are looking at, so a copied link opens the same rows for a
colleague. Bulk delete moves into the toolbar, and a location something
already uses cannot be selected; the checkbox carries the reason.
## A rule threshold can be stated per unit
A rule target can carry a per-unit basis, so the row reads as the intensity it
states — `≤ 8 kg CO2e / m2`. Pick the dimension beside the threshold's own
unit and the rule divides by the matching amount the LCA records; a divisor
that is missing, zero, negative or in another dimension reads as not
applicable and names which.
## Rules can target a product's area, volume, length or lifetime
Area, volume, length, technical life and reference service life are now
offered as rule targets whether or not the group's PCR declares a key of that
name. Each is stated in the unit you would normally read it in, so a lifetime
threshold is authored in years rather than seconds.
## A formula can combine units that multiply out to its target
A formula's units are compared by what they measure rather than how they are
spelled, so `power consumption × reference service life × coefficient = energy
used` can be saved, and bounding an ADPf formula by a resource-use indicator
no longer refuses over the two spellings of the megajoule.
## Notes and evidence on the mass balance
Every control on the mass balance, and the balance as a whole, takes an
annotation: a Note for why the model reads the way it does, or an Evidence for
what backs the claim. Either can carry files — a lab report, a supplier
statement — or none at all. Notes stay internal to your company: they never
appear on a declaration, through the API or behind a share link, and they
never change a verdict, so an explained deviation still reads as failing.
## Saved views on the LCA groups list
The LCA groups list now has a filter bar, URL-bound sorting and paging, and
saved views. Save the columns, filter and sort you use as a named view, keep
it to yourself or share it with your company, and switch between views from
the tabs above the table. The address bar carries whatever you are looking at,
so a copied link opens the same rows for a colleague.
## Duplicating or versioning a product carries its declaration
A product copy now takes every declaration the source holds with it. A new
version continues a locked declaration's chain; a plain duplicate starts a
fresh one. The registration identity does not carry — registration and
declaration numbers, issue date and valid-until are cleared, and the copy
starts as a draft.
## Activity imports match an existing supplier by name
An activity CSV naming a company without its id created a new company on every
import instead of matching the supplier already there, and missed that
supplier's product too. The name is now matched case-insensitively, and
accepted when it resolves to exactly one company. A row naming no company at
all resolves to the single product matching its SKU, whatever supplier owns it.
## A negative line quantity reads as a net input
Closed-loop production scrap re-fed into the next run never crosses the system
boundary, so a practitioner states it as a negative quantity on its own line.
That was treated as an unresolvable weight, which voided the mass balance for
the whole model; it is now netted, the way modeled weight and the impact
engine already do. A weight rule matching such a line measures the net instead
of skipping the line, so its verdict can change.
Feature flag
## Rules can measure impact per functional unit
The parameter picker offers every indicator twice — once on the declared unit
and once per functional unit — and commits the basis with the indicator, so
two rules on the same indicator read differently in the list. A functional-unit
target is measured against the LCA's own declaration.
Feature flag
## A formula rule can be bounded from both sides
A formula field takes two comparisons as well as one, bounding the middle from
both ends — `2 < metals + packaging <= 5`, or
`transport <= metals <= 2 * transport`. Each end keeps its own strictness, so
`<` and `<=` mean different things on the same line, and each may be a plain
number or an expression over the same variables, resolved per LCA. A bound
that measures nothing drops the cell to N/A naming which side, and two bounds
that resolve to an empty range report N/A rather than failing every member.
Feature flag
## Mass balance on the product weight card
A LIVE product's Total weight card opens a mass balance: a signed table of
every flow — product, packaging, ancillary and waste — across A1 to C, with
per-row and per-column sums, the weights you declared beside them, and each
row expandable to the elements underneath. Seven controls read that table and
say what does not reconcile, within a ±1% tolerance; hovering one outlines the
cells it read. Basis is gross weight as modeled, waste counts only what the
model states explicitly, and a lane's cargo is shown but never summed. The
footer exports the matrix and the controls as one CSV.
Feature flag
## Published EPDs state both unit bases
An EPD whose results are declared per functional unit now sends its
declared-unit results too, as a second set in every result section — impacts,
resource use, output flows and carbon flows — each naming its own basis. The
weights and dataset amounts elsewhere in the document are stated per declared
unit, so a reader comparing them against the results was comparing two bases.
Documents whose conversion factor is 1 are unchanged.
## Process lines no longer carry weight they never deposited
A kilogram on a process line is the impact factor's basis — impact per
kilogram processed — not matter the line adds to the product. It was being
counted as weight anyway, inflating inventory weight totals, the model CSV's
`Input_Weight` and any rule measuring weight. Waste lines are unaffected, as
they are matter genuinely leaving. Weights on affected models will drop.
## Rules can be reordered by dragging
The rules list keeps the order you put it in — drag a rule by its handle to
move it. Feature flag
## A rule can be bounded from both sides against another parameter
A range rule measured against another parameter now takes both a lower and an
upper multiplier, so "packaging is between 2% and 5% of declared weight" is one
rule rather than two. Feature flag
## The model CSV separates top-level items with a blank row
The product model CSV export writes a blank row before each top-level item
after the first, so you can see where one item's block ends and the next
begins. A script parsing the file needs to tolerate the spacer rows; the JSON
export is unchanged.
## Deciliter (dl) is a supported unit
Quantities can be entered and converted in deciliters.
## Non-material rows export N/A as their material group
A row whose element sits outside the material taxonomy — an energy or waste
line — now exports `N/A` in the model CSV's `Material_group` column, the way
the model page already renders it. The stored value is untouched, so
composition tables, program-operator submissions and rule filters read what
they read before.
## The model CSV's notes column is now Input\_Notes
The column carrying a line's notes was headed `Input_Name`, which is not what
the model page calls it. It now reads `Input_Notes`, and the default leg names
the page leaves blank are blank in the file too. A spreadsheet or script keyed
on the old header needs updating.
## Nested material groups name their whole chain
The model CSV's `Material_group` column named a nested PCR group by its leaf
alone, so `Control gear` gave no hint that it sits under Product and two groups
under different parents read identically. The cell now carries the chain —
`Product > Control gear`.
## Lines measured in flow units publish no weight
A line measured in kWh, tkm, hours or money exported `Input_Weight` as `0` with
a blank unit. It now exports nothing, while a line genuinely measured as 0 kg
keeps its zero.
## Very small quantities read as themselves
A quantity below the display precision — `0.0000217`, say — showed as `0` in
the editable field while the weight cell beside it showed the real value. Both
now extend the precision to the first significant digit.
## Milliliter (ml) is a supported unit
Quantities can be entered and converted in milliliters.
## Rule filters take a set of taxonomies and stages
A rule's filter can name several taxonomies, life cycle stages or elements at
once instead of one each, so "packaging at A4 or A5" is a single rule. Two new
dimensions come with it — **Life cycle stage** and **Material flow** — and the
filter builder toggles the members of a set in place.
Feature flag
## Nested inventory rows show the stage they inherit
On the product's Inventory tab, a row nested under a bundled line now reports
the stage it takes from that line, matching the model page and the CSV. The tab
published each row's own stage, so the same model told two stories about where
its lines sit.
## The model CSV names material groups the way the page does
The product model CSV export's `Functional_Group` column is now
`Material_group`, and its cells carry the display name the model page shows —
`Control gear`, not `controlUnit`. A spreadsheet or script keyed on the old
header or the stored values needs updating; the API's own `materialGroup`
field still reads and writes stored values.
## Nested model rows show the stage and material group they inherit
On the model page, a row nested under a bundled line shows the stage it takes
from that line as a read-only chip, and a row that takes its material group
from the line above shows that group the same way; hovering either names the
line it came from. Both cells previously offered a selector for a value the
row doesn't publish, so the page disagreed with the CSV beside it.
## Nested product rows are exported under the parent line's material group
A nested row whose own material group is **Product** now reports the group of
the line that pulls it in. A sub-model's steel under a Packaging line counts
as packaging — in the EPD's packaging table, in the material composition
published to program operators, and in the CSV — rather than as part of the
product's own composition.
## Mix constituent figures carry the unit they were computed in
A mix constituent's quantity and weight in the model CSV export were labelled
with one unit and computed in another, agreeing only when the mix happened to
be consumed in its dimension's base unit. Quantities now convert into the unit
the mix is consumed in, so one mix's constituent rows add up to the mix
quantity.
## Bundled sub-model rows are exported under the bundling line's stage
In the model CSV export, the rows of a bundled sub-model are reported at the
stage of the line that bundles them, matching the single cradle-to-gate total
the calculation books for that line. Rows the parent's stage doesn't cover are
left out rather than moved.
## Weight on the product Inventory tab
The Inventory tab has a **Weight** column: what the model consumes of each
element, in kg. A cell is empty where the element carries no weight to report
— a transport leg, a use-stage line, an element measured in kWh or hours — and
reads `--.--` where a weight should exist and none was found.
## The model CSV export follows the page's life cycle order
Rows in the product model CSV export are ordered by life cycle section, the
way the model page groups them, so the file and the screen can be read side by
side. They previously came out in raw input order, mixing the stages together.
## The Inventory tab reports what the whole model consumes
The product Inventory tab's Quantity column totals each element across the
model, scaled by the quantities of every parent above it. A source inside a
sub-model the model uses twice previously showed the per-parent figure.
## The use-stage line recalculates when the PSR or a product-sheet value changes
Selecting a PSR on a Declaration, or entering a product-sheet value its
equation reads, recalculates the use-stage (B6) line. The stored result
previously stayed as it was until someone edited the use stage by hand, so a
Declaration could report a use-stage impact that no longer matched its inputs.
## Verification findings are grouped, and warnings read as advice
The submit-for-verification dialog groups repeated validator findings into one
line with an occurrence count, keeps the requirement titles behind a
disclosure, and separates warnings and notices from the findings that actually
block submission. One real refusal that filled the dialog with 221 red bullets
came from 3 distinct problems.
## Verifiers can download the product export
**Export CSV** and **Export JSON** on a product page were disabled for the
verifier role, which is the one role whose job is to take the model off the
page and check it. Both are available to verifiers now.
## Rules can target a value from the product sheet
A rule can measure any attribute the group's PSRs declare on the product sheet,
compared in the dimension the PSR declares it in. Keys declared as an enum or a
boolean are compared for equality against a declared option instead of a
threshold. A declared key no member has answered is still offered — a rule
against it is how a verifier finds the members that are missing the value.
Feature flag
## Rules can measure energy use
An **Energy used** parameter measures what energy and process elements consume,
so "B6 electricity is at most 400 kWh" is now an expressible constraint.
Feature flag
## The activity list filters by dataset status
The **Dataset status** filter reaches the Activity list, which is
server-paginated and so needed the filter in its query rather than in the
browser.
## Rules can be narrowed to element categories, and measure transport
An LCA group rule can be narrowed to the element categories it applies to, and
the parameter picker offers only what those categories can measure. Two
transport parameters — **cargo weight** and **distance** — read off the
transport line. Feature flag
## Weight parameters name which weight they mean
The rule picker's weight parameters are grouped under **Modelled weight** and
**Declared weight**, each offering Product, Packaging and Total. Declared
packaging, declared total and modelled packaging are new; **Declared weight**
is renamed **Declared product weight**, which is what it always measured —
`Product.weight` excludes packaging. Rules already written are unaffected.
Feature flag
## Filter element lists by dataset status
The **Dataset status** filter — Assigned or Unassigned — reaches the Inventory
list and its element lists, and the Products list, not just the product
Inventory tab it shipped on. The Dataset list withholds it: every row there
reads as assigned, so the filter would return every row or none.
## Filter element lists by LCA group
Every element list — the inventory pages, a product's own inventory panel, an
LCA group's buckets — takes an **LCA group** filter, narrowing the list to the
elements that group governs. Parts and transport types match on it too, not
just products and materials. Feature flag
## Version state has its own column, and its own filter
Version state moves out of the name cell into a column of its own on every
element list, and a **Version state** filter narrows a list to draft, in
review, locked, verified, or rejected. The company Inventory list — the one
place every element appears — never showed a state at all before. The
"Where it's used" tree shows each row's state as well.
Feature flag
## Find everything that uses an element or dataset
A **Uses element or dataset** filter narrows a list to the elements that consume
a chosen one — so a dataset or material can be traced to every model built on it.
A depth toggle picks between **Used directly** and **Used anywhere in the model**,
which is the default, so a dataset three levels down the bill of materials still
matches. The filter is on the inventory lists, an LCA group's buckets and LCAs
tab, and a product's own inventory panel.
## Change version state for a whole selection
A verifier who selects rows in an element list gets a **Change state** selector
in the bulk toolbar, carrying the same transitions, confirmation copy, and
rejection-reason rule as the single-item path. Each option names how many of the
selected rows are eligible for that transition, and an option no row is eligible
for can't be picked. Feature flag
## Documentation files on every element
Every element — product, model, sub-model, material, dataset, or electricity —
now has a **Documentation** section in its settings, holding any number of
supporting files with an editable comment on each. It's where the rationale
for a dataset choice, and what else was considered, belongs; previously the
Product sheet slot was the only general-purpose place to put such a file, and
datasets had no slot at all.
## Send a locked version back with a reason
A verifier can now start a review on a locked product, material, dataset, element, or
LCA group, then approve or reject it. A rejection records the reason, so a version that
was looked at and refused is no longer indistinguishable from one nobody has picked up
yet. A rejected version stays immutable and is kept for the record — the fix path is a
new version — and a verifier who rejected by mistake can un-reject it.
All four actions belong to the Verifier role, and marking a locked version verified
outright still works: a review is a path, not a step you have to take.
Feature flag
## Two new version states in the API
`versionState` on product, material, energy, process, and dataset reads can now be
`in_review` or `rejected`, alongside `draft`, `locked`, and `verified`. If your
integration branches on this field, handle the two new values — until now the field was
left out of the response entirely for a version in either state. It is also always
present now: records created before versioning report `draft` rather than omitting it.
`versionState` is documented in the API reference for the first time.
## Submit an EPD to SmartEPD for verification
A "Submit for verification" action on the EPD sync panel sends the current
document to SmartEPD's review queue, without leaving Variable to do it by
hand. It's available once the EPD has been synced to SmartEPD at least once.
Submitting requires the EPD's governing LCA group to be Verified — if it
isn't, the dialog says so and nothing is sent. A confirmation step names the
EPD before sending; there's no way to withdraw a submission afterward.
## Fuel consumption and gross density are entered as rates
Two fields on a transport scenario (A4) asked for the wrong kind of quantity.
Fuel consumption asked for a volume of fuel and transported product gross
density asked for a weight, but SmartEPD reads them as l/100km and kg/m³ — so
a gross density entered as 2,500 kg was published as 2,500 kg/m³.
Both now ask for the rate itself, in a plain box with the unit named in it.
There's no unit to choose, and the number you type is the number that's sent.
Whatever was entered in those two fields before this change is no longer shown
and is no longer sent. Enter the rate on any transport scenario that had one,
then re-sync — EPDs synced earlier keep the values they were sent with.
## Scenario amounts sync to SmartEPD in the right unit
Amounts on your scenarios — transport distances, end-of-life masses, product
weight — are now converted into the unit SmartEPD expects before they're sent.
Previously the number went across as you entered it, so a distance recorded in
miles was read as kilometres on the other side.
If a value can't be converted because it's a different kind of quantity than
the field asks for — a weight where a length belongs — the field is left out of
the sync and the reason is reported, rather than sending a number that would
read as plausible but wrong.
EPDs you synced before this change keep the values they were sent with. Re-sync
to update them.
## Open a synced EPD in SmartEPD
The SmartEPD sync popover on an EPD now titles itself with the operator's name,
and that title links straight to the EPD in SmartEPD instead of leaving you to
go find it there yourself.
The link is live as soon as a sync succeeds, and only once SmartEPD has an EPD
to point at.
## Breadcrumbs survive a reload
The breadcrumb trail in the header now stays put when you reload the page, so
you keep the path you took to get where you are instead of starting over. The
recent-history menu beside it is restored the same way, and it carries across
tabs rather than being rebuilt in each one.
Both are cleared when you sign out or switch companies.
## A more compact record header
The header on LCA, inventory, and dataset records can now be collapsed to a
single compact row — name and status alongside inline impact, weight, and
data-quality summaries. Use the toggle beside the page actions; on LCA records
you can also click the border under the tabs. The header opens expanded as
before, and your choice is remembered across records.
The "Last updated" line has moved off the bottom of the LCA record page. The
same history is available from the "Edited:" link in the record's settings
panel.
## Viewers and verifiers can see suppliers and customers
The **Viewer** and **Verifier** roles now have read-only access to your
suppliers and customers by default, matching the Contributor role. None of
these roles can create, edit, or delete supplier and customer records — that
stays with owners and admins. Account owners can still adjust the default per
role under Access Control.
## Assign a supplier to many inventory items at once
Select items in your inventory, choose **Assign supplier** from the bulk
toolbar, and pick or create the supplier. By default only items missing a
supplier are filled in — items that already name one are skipped, and the
count is shown before you confirm. Tick **Overwrite existing suppliers** to
re-point every selected item instead. Bulk assignment writes to drafts only.
See [Assigning a supplier in bulk](/docs/help/assign-supplier).
## Set a technical life alongside reference service life
Products can now carry a **technical life** — the expected physical life of a
product — as a time quantity, separate from its reference service life. Enter it
in product settings, and your PCR equations can reference it as a named variable
to drive the functional-unit calculation, B6 operational energy, and
coefficient-based EPD variants for products whose technical life differs from
their declared reference service life. When set, it appears on the EPD report
next to reference service life.
## Verify locked versions with the Verifier role
Teams can now sign off on locked versions. A new **Verifier** role grants
read-only access plus one capability — marking a locked version as
**Verified**, the highest-confidence state, recorded in the audit history with
an optional note. Assign the role from Users & roles, then use **Mark as
verified** on a locked version. See
[Product and material versioning](/docs/help/versioning).
Feature flag
## Set a dataset on an element through the API and MCP
You can now bind an emission-factor dataset to a sourced element — material,
energy, or process — in the same call that creates or updates it, instead of
only reading the binding back. Send `dataset: { uuid }` (plus an optional
`datasetAmount`) to point the element at a dataset; binding one makes the
element external (`dataSource: external`), and sending a different dataset on
update re-points it. Use `/v1/database` to find a dataset to bind.
These writes are available through MCP as well, alongside the rest of the
element write contract: `weight`, `packagingWeight`, `recycledPercent`,
`dataSource`, `supplierPartId`, `cpcCode`, and `dataQualityIndicators`. Custom
transport modes accept their backing dataset as `dataset` too (the older
`footprint` field still works but is deprecated). See [Datasets](/docs/dataset)
and the [Material](/api-reference/v1/material/post) reference.
## Justify your data quality ratings
You can now record a free-text justification alongside your data quality
ratings — why those ratings were chosen, which production years and background
datasets the figures rest on, and any caveats a reviewer should know. It reads
and writes through the API and MCP as `dataQualityIndicators.description`, on
both products and materials, and is editable in the Variable app next to the
ratings it explains. See [Data quality](/docs/help/data-quality).
## Set data quality indicators through MCP
Data quality indicators are now available through MCP, matching what you can
already do in the Variable app. When you create or update a product or
material, you can record the documentation year, the share of the footprint
backed by primary data, the share backed by supplier-specific data, and the
five data quality ratings (technological, temporal, geographical,
completeness, and reliability). The average data quality score is still
calculated for you. See [Data quality](/docs/help/data-quality).
## Bundle or unbundle optional benefits (D)
Bill-of-materials lines placed at **optional benefits (D)** can now toggle
between bundled and unbundled, the same as every other stage group. Bundled
(the default) imports the source's cradle-to-gate (A1–A3) total stamped onto
module D; unbundled imports the source's own module-D value — its recycling or
recovery credit — into stage D. Existing inputs are unchanged: the default
stays bundled. See [Life-cycle stages explained](/docs/help/life-cycle-stages).
## Richer dataset reference on custom transport modes
A custom transport mode is backed by a single freight emission-factor dataset,
and its `dataset` reference now carries the same detail as external elements:
alongside `uuid` and `name`, read responses include the `source` database and
the `declaredUnit` (a freight unit — tonne-kilometre or tonne-mile). Built-in modes have no backing dataset
and omit it. There's no `datasetAmount` — a mode's factor is a unit quantity in
its declared unit, so it adds nothing beyond `declaredUnit`.
The enriched reference also surfaces through the MCP `get_transport_mode` and
`list_transport_modes` tools. See the
[Transport mode](/api-reference/v1/transport/mode/list/get) reference.
## See the dataset behind an external element
External elements — materials, energy, and processes that get their impact from
a single emission-factor dataset (`dataSource: external`) — now tell you which
dataset that is. Read responses include `dataset` (the backing dataset's `uuid`,
`name`, `source` database, and `declaredUnit`) and `datasetAmount` (the quantity
and unit consumed). Both are absent for modeled products. If you narrow a
response with `return`, list `dataset` / `datasetAmount` there too or they're
omitted.
The same fields surface through the MCP element tools (`get_material`,
`list_materials`, and the energy/process element tools). See the
[Material](/api-reference/v1/material/get),
[Energy](/api-reference/v1/energy/get), and
[Process](/api-reference/v1/process/get) references.
## Absolute impact contributions in the inventory tab
In the app, a product's **Inventory** tab now shows each element's **absolute**
contribution to the model — its intensity scaled by quantity into the model's
stages — instead of a per-unit intensity, so you can see what every material,
process, and transport segment actually adds to the product's footprint. The
GWP-fossil contribution shows for everyone; a missing value stays a dash and a
real zero shows zero.
With all impact indicators enabled, a selector above the table swaps the impact
column across every EN 15804+A2 indicator the model exposes — each in its own
unit — defaulting to GWP-fossil. See
[Environmental impact indicators](/docs/help/impact-indicators). Preview
## Attach transport to a model input
You can now attach a freight
[transport lane](/api-reference/v1/transport/lane/list/get) to a model input
through the public API and MCP — modeling the transport leg of a product's bill
of materials end-to-end without dropping into the app. Send a `transport` block
on `POST` / `PATCH` (and the bulk input endpoint) with the `lane` to follow and
either the material input it carries (`for`) or an explicit cargo `weight`; send
`transport: null` to detach.
When you set `for`, the leg takes its lifecycle stage and cargo weight from the
carried material, so `weight` is derived rather than sent. Reads echo the lane,
weight, carried material, and the derived tonne-kilometres (`tkm`); the leg's
carbon footprint flows into the model total. The MCP `create_model_input` and
`update_model_input` tools gain the same field. See
[Models & Inputs](/api-reference/v1/model/inputs/post).
## Deleting an import undoes the field updates it made
Deleting a product or material import no longer just removes the items it
created — it also **reverts the scalar updates** that import made to
pre-existing items. Fields it overwrote are restored to their prior values, and
blanks it filled in are cleared again. The revert is guarded: any field you (or
a later import) changed after the import is left untouched, so a delete never
clobbers newer edits. When some fields are skipped for that reason, the app
tells you how many couldn't be reverted. See
[Undoing an update](/docs/import/import-behavior#undoing-an-update).
## Material imports default to filling in blanks
Material imports now default to **Fill missing only**, so re-importing a file
backfills blank fields — weights, CPC/HS codes, descriptions, notes — on
materials you already have, without overwriting anything you've already set. A
stored `0` or empty value still counts as set and is kept. Product and other
import types are unchanged and still default to **Create new only**; you can
switch either to any mode on the upload screen. See
[Import match mode](/docs/import/import-behavior#import-match-mode-products-and-materials).
You can also see which existing products an import updated, not just the ones it
created: an import's detail view now lists updated products under their own
**Updated** tab.
## Energy and process elements in the public API
You can now manage energy and process elements through the public API and MCP,
the same way you already manage materials: list them, create and update them,
upload an image, and delete them — one at a time or in bulk. New `/v1/energy`
and `/v1/process` endpoints mirror `/v1/material`.
An energy or process element behaves like any other element; what makes it
"energy" or "process" is simply its place in the taxonomy. Creating one files it
under the matching category automatically, and you can pass a `taxonomy` to pick
a more specific subcategory.
`GET /v1/material` now returns materials (and any uncategorized elements), so
energy and process elements no longer appear there — reach them through their own
endpoints. See the [Energy](/api-reference/v1/energy/list/get) and
[Process](/api-reference/v1/process/list/get) references.
## Update existing materials and products on import
Material and product imports now have a **match mode** that decides what happens
when a row matches an item you already have. Pick it on the upload screen:
**Create new only** (the default — matched rows are skipped, today's behavior),
**Overwrite existing** (the imported value wins), or **Fill missing only** (fill
blanks, keep what you've already set). Use it to backfill enriched source data —
weights, CPC/HS codes, descriptions, notes — onto items you created earlier,
without re-keying them by hand.
Only the item's own fields are updated; its relationships — supplier, assigned
datasets, and bill-of-materials inputs — are left untouched in every mode. Under
**Fill missing only**, a stored `0` or empty value counts as set and is kept:
Variable treats zero as a value and only a truly missing field as missing.
Re-imports still match by SKU (or name), so they never create duplicates. See
[Import match mode](/docs/import/import-behavior#import-match-mode-products-and-materials).
## Set data quality indicators on materials
Material elements now have an editable data quality panel beside the dataset
passport, so you can set all five pedigree-matrix dimensions — technological,
temporal, and geographical representativeness, completeness, and reliability —
directly on a material, not only on the dataset it points to. The aggregate
data quality rating folds in all five and updates as you edit.
## Public API: set temporal representativeness directly
`temporalDQR` is now a writable data-quality indicator on product create and
update, alongside the other pedigree-matrix ratings (`technologicalDQR`,
`geographicalDQR`, `completenessDQR`, `reliabilityDQR`). Send `1` (Good — same
reporting year), `2` (Fair — less than 5 years old), or `3` (Poor — more than
5 years old).
Previously `temporalDQR` was read-only and inferred from `documentationYear`;
it's now set directly, matching the data-quality panel in the app.
`documentationYear` remains a separate, writable field for reference.
## Public API & MCP: recycled content on products
Product and material responses now carry recycled-content percentages.
`recycledPercent` is the value you declare (0–100) — a `0` is a declared zero,
while an undeclared value is absent — and `modeledRecycledPercent` is the
server-computed weight-weighted average of the contributing inputs' recycled
content, the recycled-content counterpart to `modeledWeight`.
Each model input also carries its own `recycledPercent`, inherited from the
input's source product — the source's modeled recycled content when available,
otherwise its declared value, or `0` when there is no source product or the source
has neither — so you can trace a model's recycled content line by line.
`modeledRecycledPercent` is derived from the bill of materials, read-only
(ignored if sent on create or update), and present only once a model has
contributing inputs. The same fields flow through every MCP tool that reads a
product or material.
## Public API & MCP: total modeled weight on products
Product and material responses now also carry `modeledTotalWeight` — the gross
modeled weight, the sum of `modeledWeight` and `modeledPackagingWeight` — in the
same `Amount` shape (`{ quantity, unit }`).
It's derived at read time from the two component weights rather than stored, so
it can't drift from them: a missing addend contributes nothing, the unit follows
the first present addend, and the field is present whenever at least one of the
two is. Like its parts it's read-only (ignored if sent on create or update). The
same field flows through every MCP tool that reads a product or material.
## Filter the product inventory by dataset status
The product inventory now has a **Dataset status** filter so you can narrow the
list to items that already have a dataset assigned (**Assigned**) or still need
one (**Unassigned**). Use it to find the products missing footprint data before
a bulk update. The filter applies to assignable items (products and energy);
composed (live) items count as assigned.
## Public API & MCP: modeled packaging weight on products
Product and material responses now also carry `modeledPackagingWeight` — the
server-computed sum of a model's A1–A3 packaging input weights — alongside the
declared `packagingWeight`, in the same `Amount` shape (`{ quantity, unit }`).
It's the packaging counterpart to `modeledWeight`: compare it against the
`packagingWeight` you declared to mass-balance packaging the way an EPD reviewer
would. It's derived from the bill of materials, read-only (ignored if sent on
create or update), and present only once a model has qualifying packaging inputs.
The same field flows through every MCP tool that reads a product or material.
## Public API & MCP: modeled weight on products
Product and material responses now carry `modeledWeight` — the server-computed
sum of a model's A1–A3 product-group material input weights (excluding packaging
and ancillary inputs) — alongside the existing `weight` field, in the same
`Amount` shape (`{ quantity, unit }`).
Use it to compare what a model actually adds up to against the weight you
declared: `weight` is the value you set, while `modeledWeight` is derived from
the bill of materials and is read-only (ignored if sent on create or update).
It's present only once a model has qualifying inputs. The same field flows
through every MCP tool that reads a product or material.
## Auto-assign transport across a bill of materials
Variable can now fill in transport for you. Instead of hand-attaching a lane to
every material in a model, use **Auto-assign transport** on a product — or select
several products on the product list and run it in bulk. For each material,
Variable reads the facility it's made at (or its supplier's location) and the
facility your product is made at, finds an existing transport
lane that connects those points within a radius you choose, and creates the
transport input using the material's own weight.
The closest matching lane wins; a tie with no clear winner is left for you to
resolve, and transport you've already set is never overwritten. When the run
finishes you get a summary grouped by result — assigned, no match, ambiguous, and
the rest. The default radius is 50 km, adjustable per run. Preview
## Public API & MCP: categorize model inputs
You can now read and write an input's **material group** through the
[API](/api-reference/v1/model/input/post) and MCP. Each input carries a
`materialGroup` array — common values are `product`, `packaging`, and
`ancillary`, and custom values (for example from a PCR document) are allowed.
Set it on create or update and it comes back on every read (single input and
list). This lets integrations model packaging and ancillary inputs end-to-end
instead of relying on the value Variable infers. The legacy `isPackaging` flag
stays consistent automatically.
## Public API & MCP: write a dataset's impact indicators
You can now set a dataset's environmental impact indicators through the
[API](/api-reference/v1/dataset/impacts/put) and MCP — the write counterpart to
reading them. `PUT /v1/dataset/{uuid}/impacts` takes the same keyed shape the
read endpoints return: each EN 15804+A2 indicator code (e.g. `GWP-fossil`,
`ODP`) maps to its life-cycle stage values (`A1`–`A5`, `B1`–`B7`, `C1`–`C4`,
`D`). Totals like `A1_A3` and `totalCarbonFootprint` are computed for you, and
the dataset's footprint is recalculated on write.
Indicators you omit are left unchanged, so you can fill in a dataset
incrementally. The dataset must be in draft state (newly created datasets are).
Using an MCP client, this means an assistant can now build a fully populated
dataset from a source document in one flow — create the dataset, then call
`set_dataset_impacts` to write its indicator values.
## Public API & MCP: all impact indicators in responses
Products, materials, datasets, switches, and [database search](/api-reference/v1/search/list)
results can now return their full set of [EN 15804+A2 impact indicators](/docs/help/impact-indicators)
— acidification, ozone depletion, water and energy use, waste, and the rest — not just carbon.
Impacts come back under a new top-level `impacts` field, keyed by indicator code (for example
`GWP-fossil`, `ODP`, `AP`).
Opt in with the `impacts` query parameter: `?impacts=all` for everything, or
`?impacts=gwp-fossil,odp,ap` to filter to a subset. Without it, responses return `GWP-fossil`
only, so existing integrations are unaffected. The same data flows through every MCP tool that
reads these resources.
The existing `footprint.CO2e` field is unchanged and still works, but is now **deprecated** in
favor of `impacts["GWP-fossil"]`. See [Impacts](/docs/impacts) for the response shape and query
semantics.
## Public API & MCP: manage your locations
You can now manage your company's locations — factories, warehouses, offices,
and ports — through the [API](/api-reference/v1/location/list) and MCP, not just
in the app. List the sites you already have, add new ones, update their details,
and remove the ones you no longer need. Pin a site to a point on the map so it
carries real coordinates, or keep it as a plain address. You can also set up a
location on behalf of a supplier you work with, not only your own company.
Once a location exists, reuse it anywhere you pick a place — for example the
start or end of a [transport route](/api-reference/v1/transport/lane/post). A
location that's still in use is protected: remove the places that reference it
before you can delete it.
Using an MCP client, just ask your assistant to list, create, update, or delete
a location.
## Public API & MCP: fetch a supplier by internalId
`GET /v1/supplier/{id}` now resolves a supplier by its `uuid`, `syncId`, or
`internalId` — the same `internalId` the API returns on the supplier — so you
can read a supplier back by the identifier you already have. The MCP
`get_supplier` tool accepts all three. `PATCH` and `DELETE` continue to require
the `uuid`.
## Public API & MCP: model freight transport
Build and reuse freight routes through the API. **Transport lanes**
(`/v1/transport/lane`) are ordered legs, each with a mode, an origin, and a
destination; distances auto-compute from the endpoints, and a lane reports its
carbon intensity (`CO2ePerTkm`) rather than an absolute total — the total
materialises once cargo weight is applied. **Transport modes**
(`/v1/transport/mode`) cover the system-curated built-ins (`truck`,
`container-ship`, `train`, the `air-*` haul bands, …) plus custom modes you
create from your own freight emission-factor datasets. Lanes support full
CRUD; built-in modes are read-only, while the custom modes you create are
fully editable.
The MCP server exposes the same operations as transport-lane and
transport-mode tools, so an agent can assemble routes end to end.
## Public API: read and write product notes
Products now expose their `notes` field through the API and MCP. `GET`
responses include `notes`, and you can set it on `POST`/`PATCH` (or via the
`create_product`/`update_product` MCP tools) alongside `description`.
## Public API: resolve addresses into transport-leg locations
Two new endpoints turn a free-text address into a referenceable location, so you
can set transport-leg origins and destinations programmatically instead of
pre-seeding them in the app. `GET /v1/geolocation?search=...` geocodes a string
like `Syracuse, NY` into ranked, un-persisted candidates; `POST /v1/geolocation`
persists a chosen candidate (by `googleMapsId`) or a manual point
(`name` + coordinates) and returns it with a `uuid`. Reference that `uuid` — or a
candidate's `googleMapsId` directly — on a transport leg.
The MCP server exposes the same surface as `geocode_location` and
`create_geolocation` tools.
## Terrestrial eutrophication now reported in mol N eq
The EP-terrestrial (Terrestrial eutrophication) impact indicator is now
reported in `mol N eq`, matching EN 15804+A2. It was previously labelled
`mol H+ eq` (the acidification unit) — a mislabelling inherited from
ecoinvent, which documents the same error in its
[v3.11 known issues](https://support.ecoinvent.org/ecoinvent-version-3.11).
The stored values are unchanged — only the unit label was corrected.
## Inputs editor: enter quantities in alternative units
The input amount field now groups units by dimension and lets you enter
a quantity in any unit the source product supports — e.g. enter a board
as `2.4 m` against an `m²`-declared product, and Variable converts to the
declared unit using the product's conversion factors. The source
product's length, volume, and area conversion factors are now editable
directly from the product preview sidebar. This is the UI counterpart to
the `declaredAmount` API shape shipped on May 22.
## Location picker: pick a location without an address
Picking a `Location` that has no attached `GeoLocation` in the
transport-leg location picker now works — previously the click was
silently dropped, blocking address-less custom locations from being used
as a segment endpoint.
## Public API: machine-readable taxonomy path on responses
Taxonomy references on `/v1/product`, `/v1/material`, and `/v1/activity`
responses now include a `path` field (e.g. `material`, `energy/electricity`)
alongside `uuid` and `name`. The path is a stable, machine-readable grouping key
— unlike the human-readable `name` — so you can reliably branch or aggregate on a
category. Purely additive; existing `uuid`/`name` readers are unaffected.
## File import: BOM rows in mixed units
BOM imports now accept an input quantity in any unit the source product supports,
even when it differs from that product's declared unit (e.g. listing a board as
`2.4 m` against an `m2`-declared product). Variable converts the value to the
declared unit for impact calculations using the product's conversion factors and
keeps your entered value in the API. Rows whose unit has no matching conversion
factor are skipped. See the
[BOM preparation guide](/docs/import/prepare-bom) for details.
## Suppliers can have their own locations
A supplier can now own one or more named locations — a factory, warehouse, or
port, each with its own address — managed from the new **Locations** tab on the
supplier page. Mark one as the default (the first one added is set automatically),
and it's used wherever a supplier needs a single address. Supplier locations also
appear anywhere you pick a place, such as the origin of a transport leg, searchable
by supplier name, so you can reuse *"Acme — Rotterdam warehouse"* instead of
re-typing the address.
## Public API: fetch a single model input
New `GET /v1/model/{modelId}/input/{inputId}` returns one input on a given model.
Responds with `404` when the input does not belong to the model (or does not
exist for this caller). The response carries the same `quantity` / `unit` /
`declaredAmount` shape as the list endpoint.
The MCP server exposes the new endpoint as a `get_model_input` tool, alongside
refreshed `list_model_inputs`, `create_model_input`, and `update_model_input`
descriptions that surface the alternative-unit semantics introduced last week.
## Owner-controlled AI features toggle
Company owners can now enable or disable AI features for their company directly
from **Company Settings** with the new "Enable AI features" switch. Previously
this required Variable support to flip the company-level `DisableAI` flag.
## Activities anchored in your company timezone
Activity dates now honor a `Company.timezone` (IANA, defaulting to
`Europe/Oslo`), so partial inputs like `"2026"` or `"2026-03"` resolve in the
company's timezone instead of UTC. Period filters (YTD, MTD, specific years)
bucket from the same timezone, and each `Activity` exposes a new `timezone`
field recording the zone used on write.
## Public API: alternative-unit response shape on `/v1/model/{id}/input`
GET, POST, and PATCH responses on `/v1/model/{id}/input` now return the `quantity`
and `unit` you supplied, even when those units are in a different dimension than the
source product's declared unit (e.g. sending `kg` for an `m3`-declared source product
with a weight conversion factor). A new read-only `declaredAmount` field carries the
canonical converted amount in the source product's declared unit — this is what
impact calculation uses. `declaredAmount` is present only for cross-dimensional
inputs; same-dimension entries (e.g. `l` against an `m3`-declared product) are
unchanged. `declaredAmount` is ignored if supplied on POST/PATCH.
## Bulk delete activities from the activity list
Activities can now be deleted in bulk directly from the Activity list. Select activities on the
Draft or Rejected tab and use the new Delete button in the floating bulk action bar.
## Admin-managed maintenance windows for self-hosted
Self-hosted operators can now schedule and run maintenance windows from
**Admin -> Maintenance windows** instead of toggling an environment variable
and redeploying. Configure a recurring window (daily, weekly, or monthly in
any IANA timezone) with an `HH:mm` start and a duration up to seven days, or
hit **Start now** for a one-off window.
## Offline ISO-3166-1 country coverage for self-hosted
The seeded `GeoLocation` dataset now covers the full ISO-3166-1 country and territory set (plus
Kosovo, which Google Maps treats as `XK`). Self-hosted instances can resolve any country offline
without a Google Maps API key. Each row ships with the same `googleMapsId`, lat/lng, and bounds
that Google's Geocoding API returns, so locations created via seed match locations created via
online lookup.
## BOM import: supplier matching and XLSX precision fixes
BOM imports now match existing suppliers by `supplierId` (internalId) alone when no
`supplierName` is provided, so id-only rows correctly link to suppliers already in your
account instead of falling through and creating products without a supplier link. The
supplier lookup also no longer caps at the first page, so accounts with more than 250
suppliers match reliably.
XLSX imports now preserve full numeric precision (previously, cells like `0.0004`
displayed as `0.000` were silently truncated to the display string), normalize date
cells to ISO strings, and drop spreadsheet error sentinels (`#REF!`, `#N/A`, `#VALUE!`,
etc.) instead of importing them as literal strings.
## Bulk location import
Users can now bulk-create or update Locations from CSV or XLSX files. The Locations page
exposes an Import action that maps name, address, and type columns; re-imports merge on
name within the company so existing values are preserved when a column is left blank.
Addresses are geocoded in the background after import.
## SmartEPD integration
Companies can now connect Variable to SmartEPD v2 and push EPDs directly from the platform.
Owners configure the connection in Company Settings → Integrations by providing their SmartEPD
API key and selecting a workspace. On each EPD, a "Sync to SmartEPD" action lets users choose a
PCC and project (creating a new project if needed) and submit the EPD to SmartEPD. A sync status
badge on the EPD page shows the current state (pending, syncing, synced, failed, or out of date),
and a "Check status" action polls SmartEPD for the latest review state. Once an EPD is approved
or published in SmartEPD, Variable automatically locks the EPD to Verified or Published to mirror
the remote lock. If the EPD is edited locally after its last successful sync, the auto-lock is
skipped and the record is marked `out_of_date` instead.
## Dataset resource in the public API
The public API now supports managing datasets in your account via `/v1/dataset`. You can list, fetch, create,
update, upload images, and delete datasets - whether they were user-created, added from the Variable Database,
or shared by a supplier. Deleting a dataset that came from the Variable Database or a supplier removes it from
your account while leaving the original in place; user-created datasets are permanently deleted.
New `DATASET_READ`, `DATASET_CREATE`, `DATASET_UPDATE`, and `DATASET_DELETE` permissions gate these routes.
The same operations are exposed as MCP tools (`list_datasets`, `get_dataset`, `create_dataset`, `update_dataset`,
`delete_dataset`), and dataset payloads now use dedicated `Dataset`, `NewDataset`, and `EditDataset` schemas in
the OpenAPI spec instead of reusing `Material` / `NewProduct` / `EditProduct`.
`dataSource` is no longer part of the Dataset surface. The previously documented `dataSource: "database"` value
has been removed from dataset responses, and sending a `dataSource` field in a dataset request body has no effect
* the server always treats the resource as a dataset based on the route. Clients that read or write `dataSource`
on the `/v1/dataset` endpoints should drop the field.
The `ProductDataSource` enum used on `/v1/product` and `/v1/material` is narrowed to `"model"` and `"external"`.
The `"database"` value is no longer accepted on those routes - datasets now have their own endpoint.
## Organization assignment on user invite
Admins can now assign an organization when inviting new users. The org selector appears in the invite
modal and the backend creates the membership in the same transaction as user creation.
## Role-based access control
A new [Access Control](https://app.variable.global/access-control) page gives a clear overview of what each role
* Owner, Admin, Contributor, and Viewer - can access across your organization. Permissions are organized into five
categories:
* **Products** - LCAs and Declarations (All roles)
* **Inventory** - Materials, Energy, Transport, Process, Datasets, and Activities (All roles)
* **Exchange** - Elements, Suppliers, Customers, Requests, and Deliveries (Owner and Admin only)
* **Reports & Plans** - Analyze, Report, and Plan (Owner and Admin only)
* **Account** - Org, Users, Locations, Company Settings, and more (Mixed access)
## Extended org access control
Organization-level access control now covers transports, datasets, parts, and EPDs in addition to products and
materials.
## Self-service dataset copying
Users can duplicate database datasets into their own company, enabling custom modifications. Preview
## Second indicator in modelling
The modelling screen now supports a secondary impact indicator alongside GWP, allowing side-by-side comparison of
environmental impacts. Sorting by the second indicator is also supported.
## Taxonomy enforcement and dataset alignment
Taxonomy alignment is now enforced when assigning datasets to inventory items. A mismatch indicator shows
when a dataset's taxonomy differs from the input and offers a one-click update action.
## PERM/PENRM calculation for classified materials
Impact calculations now include PERM (primary energy from renewable materials) and PENRM (primary energy from
non-renewable materials) for material inputs classified with flow types.
## BCCP and BCCAP rules
The impact calculation service now applies biogenic carbon content rules (BCCP for products, BCCAP for packaging)
based on functional group classification, per EN 15804+A2.
## Geographic filtering
Search results can now be filtered by geography, making it easier to find region-specific datasets and products.
## A1-A3 entry mode toggle for custom datasets
Custom datasets now support toggling between individual A1, A2, A3 stage entry and a combined A1-A3 entry mode.
## Decimal separator override for data imports
Data imports now support explicit decimal separator selection, resolving ambiguity with European-format CSV files.
## Self-hosted data package delivery
Self-hosted instances now receive emission factor data packages via mounted volumes, simplifying data updates.
## Versioning in Public API
The public API now supports versioning for products and materials. Create new versions, list version history,
and fetch specific versions via the API. Bulk endpoints updated accordingly. Preview
## `varId` added to Public API
All API responses now include the `varId` field - a human-readable, short identifier for each entity.
Preview
## BOM import adapters
A new adapter pattern for BOM imports allows external formats (e.g., SAP S/4HANA) to be automatically
transformed into the expected CSV structure on upload.
## Bulk endpoints in Public API
New bulk create, update, and delete endpoints added for products, materials, and activities, allowing efficient
batch operations via the API.
## Manual geolocation entry
Users can manually enter geographic coordinates with source tracking, useful when automated geocoding is
unavailable.
# Activity
Source: https://docs.variable.global/docs/activity
An `Activity` represents the actual flow of materials through the system. Activities are a quantity of a `Product` or `Material`, with a resulting CO2e value.
[Activity API documentation ](/api-reference/v1/activity)
### Attributes
| Attribute | Type | Description | Example |
| :- | :- | :- | :- |
| uuid | string | The unique identifier | 123e4567-e89b-12d3-a456-426614174000 |
| description | string | A description of the activity | Steel purchased |
| direction | string | The direction of the activity | in |
| supplier | Supplier | The supplier of the activity | `{ uuid: "...", name: "Steel Co" }` |
| customer | Customer | The customer of the activity | `{ uuid: "...", name: "My Company" }` |
| quantity | number | The amount of the activity | 100 |
| unit | Unit | The unit of measure | kg |
| startDate | date | The date the activity is valid from | 2021-01-01T00:00:00.000Z |
| endDate | date | The date the activity is valid until | 2021-12-31T00:00:00.000Z |
| timezone | string | IANA timezone used to anchor the dates | Europe/Oslo |
| product | Product | The product for the activity | `{ uuid: "...", name: "Steel" }` |
| scope | Scope | The scope of the activity | `{ uuid: "...", name: "Scope 2: Electricity" }` |
| taxonomy | Taxonomy | The category of the activity | `{ uuid: "...", name: "Steel" }` |
| org | Org | The org unit responsible for the activity | `{ uuid: "...", name: "Procurement" }` |
### Date anchoring
Under-specified date inputs (`"2026"`, `"2026-03"`, `"2026-03-15"`) are anchored
to your company's IANA timezone, set on the `Company` resource and defaulting to
`Europe/Oslo`. `"2026-03"` from a company in `America/Los_Angeles` resolves to
the start of March 2026 in LA, not UTC. Each `Activity` records the timezone
that was used on `Activity.timezone` for provenance; period filters (YTD, MTD,
specific years) bucket from your company's timezone.
To avoid start/end snapping, supply a full ISO-8601 timestamp
(`2026-03-01T00:00:00`). The wall-clock time is interpreted in the company's
timezone — an offset in the input (`…-08:00`, `…Z`) does not override it.
### Diagram
```mermaid theme={"system"}
erDiagram
Activity ||--|| Product : "for"
Activity ||--|| Taxonomy : "in"
Activity ||--|| Org : "in"
Activity ||--|| Scope : "in"
Activity ||--|| Supplier : "from"
Activity ||--|| Customer : "to"
Activity {
quantity number
unit Unit
CO2e decimal
startDate date
endDate date
}
```
# Data model
Source: https://docs.variable.global/docs/data-model
### Products, Elements & Datasets
**Datasets** are the original source of impact data (emission factors). They can come from databases like ecoinvent, be shared by other companies, or be created by the user.
A **Product** (also called an LCA) is a modeled footprint - it has a bill of materials (a `Model` with many `Input`s) and calculates its impacts from them.
An **Element** is a named, reusable source that an `Input` points to. **Materials**, **Energy**, **Transport**, and **Process** are all kinds of Element - they live in your Inventory and can be referenced by Inputs across many Products.
A **Material** is one kind of Element. It gets its impacts by referencing a **Dataset** directly rather than modeling a bill of materials. You add metadata like supplier, SKU, and location to it.
This modeled-vs-referenced distinction applies to every kind of Element - Materials, Processes, Energy, and Transport can each either be modeled (with their own BOM) or reference a Dataset.
| API endpoint | What it returns | Description |
| :- | :- | :- |
| `/product` | Products | Modeled footprints with a bill of materials |
| `/material` | Materials | Sourced items that reference a Dataset for their impacts |
| `/dataset` | Datasets | User-created, supplier-shared, or database-added emission factor data |
| `/database` | Datasets | Search the full Variable emission factor database |
### Models & Inputs
An `Input` is a line item in a Product's bill of materials. It carries a quantity and unit, and points to an Element - a `Material`, `Process`, `Energy`, or `Transport` - or, in some cases, to another `Product` directly. The Input's calculated CO2e is its quantity multiplied by the emission factor from the source's Dataset.
### Where an element is used
`Input`s point *down* from a Product to the Elements it consumes. Reading that relationship in reverse — every Product and assembly that depends on a given Element — is the **"Where it's used"** view. In the app it walks the bill of materials upward to every top-level product, grouped by company, annotated with the element's contributed amount at each level.
### Activity
An `Activity` represents the actual flow of materials through the system. Activities are a quantity of `Product`, `Material`, or `Transportation` with a resulting CO2e value.
### Diagram
```mermaid theme={"system"}
erDiagram
Product ||--o{ Model : "has"
Model ||--o{ Input : "has"
Input ||--o| Processing : "processed by"
Input ||--o| Transportation : "distributed by"
Input ||--o| "Use Stage" : "has"
Input ||--o| Material : "using source"
Material ||--o| Transportation : "transported via"
Material ||--o| Supplier : "product of"
Product ||--|| Taxonomy : "categorized in"
Material ||--|| Taxonomy : "categorized in"
Transportation ||--o| Supplier : "product of"
Product ||--o{ Activity : "distribution"
Activity }o--o| Material : "part of"
Activity }o--o| Transportation : "part of"
Activity ||--o| Taxonomy : "categorized in"
Activity ||--|| Org : "in"
Activity ||--|| Scope : "in"
```
# Datasets
Source: https://docs.variable.global/docs/dataset
**Datasets** are the original source of impact data (emission factors). They can come from databases like ecoinvent, be shared by other companies, or be created by the user.
The `/v1/dataset` endpoint lists datasets in your account - ones you've added from the Variable Database, created yourself, or received from a supplier. This is different from the `/v1/database` (search) endpoint, which searches the global Variable Database.
API documentation: [Dataset ](/api-reference/v1/dataset), [Database search ](/api-reference/v1/search)
### How datasets relate to products and materials
A **Product** (LCA) is a modeled footprint - it has a bill of materials with Inputs. A **Material** gets its impacts by referencing a Dataset directly. See [Products & Materials](/docs/material) for more.
| Endpoint | Returns | Description |
| :- | :- | :- |
| `/v1/product` | Products | Modeled footprints with a bill of materials |
| `/v1/material` | Materials | Items that reference a dataset for their impacts |
| `/v1/dataset` | Datasets | The original source of impact data in your account |
| `/v1/database` | Search results | Global emission factor database search |
### Binding a dataset to an element
A sourced element — a material, energy, or process — that draws its impact from a single dataset is an **external** element (`dataSource: external`). Create or update one through the API (`/v1/material`, `/v1/energy`, `/v1/process`) or MCP and bind its dataset by sending `dataset: { uuid }`; binding one makes the element external. Add `datasetAmount` to set the quantity its per-unit factor scales, or omit it to consume one declared unit of the dataset. Its unit must be in the dataset's dimension (`500 g` of a per-kg dataset); an amount in another dimension returns a 400. Sending a different `dataset` on update re-points the element. Use [database search](/api-reference/v1/search/list) to find a dataset to bind.
### Attributes
| Attribute | Type | Description | Example |
| :- | :- | :- | :- |
| uuid | string | The unique identifier | 123e4567-e89b-12d3-a456-426614174000 |
| name | string | The name of the dataset | Steel, converter, unalloyed |
| sku | string | The stock keeping unit | EI-3.10-12345 |
| unit | Unit | The unit of measure | kg |
| staticImpactMethod | string | The document type the dataset declares (`en15804_a2`, `unknown`) | en15804\_a2 |
| taxonomy | Taxonomy | The category | Steel |
| geoLocation | GeoLocation | Geographic scope | Europe |
| dataSources | DataSource\[] | The source databases | `[{ name: "ecoinvent" }]` |
| created | date | The date the dataset was added | 2021-01-01T00:00:00.000Z |
| updated | date | The date the dataset was last updated | 2021-01-22T00:00:00.000Z |
### Footprint
The `footprint.CO2e` object holds the CO2e values for the dataset. See [Products & Materials](/docs/material#footprint) for the full footprint structure.
# Adding inputs to your Product (LCA)
Source: https://docs.variable.global/docs/getting-started/add-materials
Inputs are how the footprint of a product actually adds up — each row is a quantity of something, and its calculated impact rolls up into the Product (LCA)'s total. This guide explains life-cycle stages and walks you through adding the different types of inputs.
**Input vs. Element vs. Dataset**
* **Input** - *single instance.* A row in a Product (LCA), with a quantity and unit. Each Input belongs to one Product (LCA).
* **Element** - *reusable.* The thing the Input points to (a Material, Energy source, Process, or Transport lane). Elements live in your Inventory and can be referenced by Inputs across many Product (LCA)s.
* **Dataset** - *reusable.* The emission-factor source attached to an Element, supplying impact values per unit.
In this guide you'll add Inputs and point them at Elements. Assigning a Dataset to each Element - covered in [the next guide](/docs/getting-started/assign-datasets) - is what produces calculated impacts.
## Understanding life-cycle stages
Life-cycle assessment divides the environmental impact of a product into stages. Variable supports the standard life-cycle stages:
| Stage | Name | Description |
| :- | :- | :- |
| **A1** | Materials | The material inputs that go into your product, plus their cradle-to-gate impacts |
| **A2** | Transport | Transport of those inputs to your manufacturing facility |
| **A3** | Manufacturing | Energy and processes at your facility, as well as waste generated during production |
| **A4** | Transport to customer | Distribution logistics |
| **A5** | Installation | Assembly and installation at customer site |
| **B1-B7** | Use phase | Product operation, maintenance, repair |
| **C1-C4** | End of life | Demolition, waste processing, disposal |
| **D** | Benefits | Credits from recycling and energy recovery |
Stages A1-A3 are often called "cradle to gate" and are the most common scope for a Product Carbon Footprint (PCF). PCFs can also extend beyond A1-A3 to cover downstream stages (A4-A5, B, C, D) when a broader system boundary is needed.
## Adding an input
In the life-cycle stage where you want to add an input (for example, **Cradle to gate (A1-A3)**), click the **+** button to open the input menu.
Select an option from the menu:
Re-use an existing element from your company Inventory
Model a new material and add it to your Inventory
Set energy usage in a production process
Add a transportation leg for distribution and storage
In B-stages, you will also see an **Add Use stage** option for modeling the operation, maintenance, or repair of a product.
Once the input is added, you can edit it directly in the row:
* **Quantity** - amount per functional unit of the product
* **Unit** - unit of measure (kg, m, kWh, etc.)
* **Stage** - move the input to a different life-cycle stage
* **Material group** - taxonomy classification used for dataset matching
For example, if the product is a chair and you use 2 kg of steel, enter "2" with the unit "kg".
## Understanding the input row
After adding an input, you will see it displayed as a row in your Product (LCA). Each row shows:
| Column | Description |
| :- | :- |
| **Element** | The named source the Input points to - a Material, Energy, Process, or Transport Element from your Inventory |
| **Notes** | Optional free-text notes for the input |
| **Quantity** | Amount per functional unit, with its unit (item, kg, m, kWh, etc.) |
| **Weight** | Calculated mass of the input |
| **Stage** | The life-cycle stage the input belongs to (A1, A2, etc.) |
| **Material group** | Classifies the input as Product, Packaging, or Other (ancillary). Additional options may appear if the LCA uses a PCR with pre-defined groups. A row whose element is not categorized under Material, or has no category yet, reads N/A here and in the model CSV. Packaging and ancillary inputs are left out of the Materials table on the EPD report and out of the material composition published to program operators — an ancillary input doesn't remain in the finished product — so the percentages there are shares of the product materials alone. When an unbundled line points to a nested model, that model's own Product lines are reported under the group of the line that pulls them in — a Packaging line's materials count as your packaging, not part of your composition — while lines set to anything else keep their own group, because that describes the component's own role. A bundled line reports as a single row under its own group, so its nested lines never appear here to be reclassified. The model page and the model CSV do list them, and there every material row among them reads the bundled line's group: a bundled Control gear model's cardboard is part of that control gear rather than separate packaging, while its energy and process rows read N/A on both, as they would anywhere else. Wherever a nested row takes its group from a line above it, its Material group cell is a read-only chip carrying that group, and hovering it names the line the group came from. To give the row a group that counts on its own, set it on the nested model's own page and unbundle the line that pulls it in. |
| **Data quality** | Aggregate Data Quality Rating (DQR) for the input, computed per the [PACT Methodology v2.0](https://www.carbon-transparency.org/) across the technological, temporal, geographical, completeness, and reliability dimensions |
| **Selected indicator** | Any additional impact indicator you choose (e.g., AP, PENRT, ODP) - switch the indicator using the column header dropdown, and toggle the value between its native unit and % of total using the unit dropdown |
| **GWP-fossil kgCO2e** | Calculated fossil carbon impact in kgCO2e |
| **GWP-fossil % of total** | The input's share of the total fossil GWP of the Product (LCA) |
| **Actions** | Edit, duplicate, or delete the input (via the `⋯` menu) |
**GWP-fossil is Variable's main indicator and is always shown.** The two GWP-fossil columns are always visible on every input row; the **Selected indicator** column is an additional slot you can swap to any other indicator (AP, PENRT, ODP, water use, etc.) for side-by-side comparison.
If the GWP-fossil column shows `--.--`, it means no value is available - typically because the Element the Input points to doesn't have a Dataset assigned yet. A value of `0` is a real result (zero impact), not missing data.
## Adding different input types
### Materials (A1)
A1 Inputs point to **Material** Elements - the materials and components you bring in to manufacture the product. Depending on your perspective that might be what you'd call a raw material - ore, timber, sand - or it might already be processed or pre-formed by the time it reaches you, like steel sheet, plastic pellets, a steel pipe, or a molded housing. They are all valid A1 Elements.
The Dataset connected to a Material Element covers its upstream production from cradle to your gate.
### Transport (A2)
A2 Inputs point to **Transport** Elements - modeled lanes from origin to destination. A lane already encodes everything about the journey: it can have one or many legs, and each leg has its own mode (truck, rail, ship, air), origin/destination, and distance. To add transport to a Product (LCA), pick (or create) a lane and set the weight of material being transported on that Input. Variable calculates the impact from the lane's legs and the weight you provide - you don't re-enter modes or distances on each Input.
### Manufacturing (A3)
A3 Inputs point to **Process** Elements covering what happens at your facility, including things like:
* Electricity usage - whether from the grid (with the appropriate regional or supplier-specific Dataset) or from on-site sources like rooftop solar, on-site wind, or co-generation. Model each source as its own Element so you can mix grid and self-generated electricity in the same Product (LCA).
* Natural gas or other fuels
* Manufacturing processes (welding, machining, coating)
* Water and wastewater treatment
* Waste generated during production - off-cuts, scrap, defects, and any other production waste, with a Dataset for the corresponding waste-treatment or disposal process
Each Input has a quantity in the appropriate unit (kWh, MJ, m3, kg, h, etc.) and points to a Process Element whose Dataset supplies the per-unit impact data for that process.
## Tips for building your Product (LCA)
If you already have a bill of materials in your ERP or PLM system, use it as a starting point. Variable can import BOMs from spreadsheets so you don't have to re-enter inputs by hand.
Prioritize the materials that make up the majority of your product by weight or cost. These typically drive the largest share of environmental impact.
Include packaging materials if they're part of the scope of the product. Add them as separate line items, and set their **Material group** column to **Packaging** so they roll up correctly in reports and EPDs.
## What's next
Each Element your inputs point to needs a dataset assigned before Variable can calculate environmental impact. Learn how to find and assign the right dataset for each Element.
Assign a dataset to each Element to supply its environmental impact data
# Assigning datasets
Source: https://docs.variable.global/docs/getting-started/assign-datasets
Datasets provide the **environmental impact data** that converts a quantity into a calculated impact - per-unit values for indicators like GWP, acidification, eutrophication, ozone depletion, water use, and more. (You'll often hear this called *emission factors*; we use the broader term because Datasets cover more than just carbon.) Most of your Inventory Elements - Materials, Energy, and Processes - need a Dataset connected before Inputs that point to them can produce a calculated footprint.
The chain is **Input → Element → Dataset**: an Input is a row in your Product (LCA) with a quantity; it points to an Element (a Material, Energy, Process, or Transport from your Inventory); the Element is connected to a Dataset that supplies per-unit impact data. Because Datasets attach at the Element level, every Input that references the same Element shares the same impact data. This guide explains how to find and connect the right Dataset for each Element.
Transport works a little differently. A **Transport** Element is a modeled lane made up of one or more legs, and the Dataset connections live on the legs (each leg has its own mode). When you add transport to an Input, you pick a lane and set the weight - you don't pick a Dataset. The workflow on this page applies to Material, Energy, and Process Elements.
## What are datasets?
Think of a Dataset as a recipe — for one unit of this material, process, or activity, here's what it costs the environment. **Datasets** contain that per-unit impact data: how much CO2e (and other environmental impacts like acidification, eutrophication, ozone depletion, water use, and energy use) are associated with one unit.
For example, a steel Dataset might indicate that producing 1 kg of steel generates 2.1 kgCO2e. When that Dataset is connected to a Material Element ("Hot-rolled steel sheet") and an Input in your Product (LCA) specifies a quantity of 10 kg of that Element, Variable calculates the impact as 21 kgCO2e. The same pattern works for electricity Datasets connected to Energy Elements (kgCO2e per kWh × kWh consumed) and natural-gas Datasets connected to Process Elements (kgCO2e per m3 × m3 consumed).
The quality of your carbon footprint depends on choosing appropriate datasets. More specific datasets (by geography, production process, or supplier) generally yield more accurate results.
## Where you can assign a dataset
Datasets attach at the Element level, but you can open the search results from several places depending on where you are in the app:
* **From an Input row in a Product (LCA)** - click the Element name in the row to open its details panel, then click **Assign dataset**. The panel also shows the Element's per-unit impact (or `--.--` if no Dataset is connected yet) and lets you add alternatives or remove the Input. Useful when you're working on a Product (LCA) and notice an Element is missing its Dataset.
* **From the Element itself** - open a Material, Energy, or Process Element from your Inventory and assign its Dataset directly. Every Input that already points to this Element picks up the change.
* **From an Inventory list** - the Materials, Energy, and Processes lists let you assign Datasets in bulk. Select multiple Elements and assign a Dataset to all of them at once. This is the fastest way to fill in Datasets after a BOM import.
Wherever you start, the assignment lands on the Element, so every Input that references that Element shares the same Dataset.
## How to assign a dataset
The flow below uses an Input from a Product (LCA) as the entry point, but the search results behave the same way whether you opened them from an Element, an Inventory list, or an Input row.
Click the Element name in an Input row to open its details panel. The panel shows the Element with its per-unit impact (`--.--` if no Dataset is connected yet). Click **Assign dataset** to open the search results.
The search results open on the **Inventory** tab, which lists Datasets your company has already added (whether imported from a database, created manually, or pulled in from a previous assignment). **Re-use one of these whenever possible** - it keeps your impact data consistent across Product (LCA)s and avoids duplicating the same Dataset under different names. Only switch to the **Database** tab if your Inventory doesn't have a suitable match; once you connect a Database Dataset to an Element, it joins your Inventory and is available to re-use across other Product (LCA)s.
Use the search box for a free-text query (e.g., "steel", "aluminium", "polyethylene"). To narrow results further, click the **+** button next to the active filter chips and add one or more filters. Filters and free-text search compose - every chip narrows the result set.
The search results reuse the standard filter component, so the same filters are available here as elsewhere in the app:
| Filter | What it narrows |
| :- | :- |
| **Footprint type** | Model, Imported, Transport, Switch, Mix, Dataset, etc. - useful for restricting to raw Datasets vs. modeled Elements |
| **Inventory** | Limit to a specific Inventory grouping |
| **Category** | Taxonomy category (Metals, Plastics, Chemicals, etc.) |
| **Source** | Data source - a specific database (ecoinvent, DEFRA, Ember, etc.) or a supplier |
| **Geography** | Region (Europe, EU27 & EFTA, Global) or country |
| **Location** / **Location type** | A specific named location, or a kind of location (factory, warehouse, port, etc.) |
| **Unit type** | Weight, volume, energy, etc. - handy when you know the Dataset must be priced in a particular unit dimension |
| **Scope** | GHG Protocol scope (1, 2, 3, Transport, Purchased goods, etc.) |
| **Org** / **Supplier** / **Customer** | The org unit, supplier, or customer the item is associated with |
| **Created by** / **User** | Filter by the original creator or by a user who has touched the item |
| **Activity group** | A specific activity grouping |
| **Uses element or dataset** | Elements that consume a chosen Element or Dataset. A second dropdown picks the depth: **Used anywhere in the model** (the default) or **Used directly** |
| **Version state** | Draft, in review, verified, etc. |
Some filters are pre-set depending on context - for example, opening the search from a Material Element typically pre-applies **Category: Material** and **Footprint type: Dataset** so you only see compatible reference Datasets. Click the **×** on any chip to remove it, or click the reset icon to clear all filters at once.
Each result row shows the key facts about the Dataset directly in the list, so you can compare options without leaving the search:
| Field | Description |
| :- | :- |
| **Name** | Full name of the Dataset |
| **Database** | Source database (ecoinvent, DEFRA, etc.) |
| **Geography** | Region the data represents |
| **Unit** | Reference unit (per kg, per kWh, etc.) |
| **GWP** | Global warming potential per unit |
| **Year** | Data reference year |
Today, clicking anywhere on a result row selects that Dataset - there is no separate "view details" interaction in the search results. Use the **Select** button on the row (or just click the row) to connect it. We plan to add a richer detail preview here in the future.
Click **Select** on the row (or click the row itself) to connect the Dataset to the Element. Every Input that points to that Element will use this Dataset for its impact calculation, with the impact computed from the Input's quantity.
## Taxonomy alignment
Variable uses a taxonomy system to ensure datasets are appropriate for your Elements. A Dataset should match or be more specific than the Element's category.
For example:
* An Element categorized as "Metals > Steel" can use Datasets for "Metals > Steel > Hot-rolled steel"
* An Element categorized as "Metals > Steel" should not use a Dataset for "Metals > Aluminium"
### Resolving a category mismatch
If you select a Dataset whose category doesn't match the Element's category, the search results flag it with a **Category mismatch** warning and show the conflict - for example, *"Your element is categorized as **Material** but this dataset is in **Energy > Fuel > Liquid > Diesel**."* You then have two options:
* **Update category & select** - click this button in the warning to re-categorize the Element to match the Dataset and connect it in one step. Use this when the Dataset is correct and the Element's category was wrong (common after a BOM import where categories were inferred from limited data).
* **Pick a different Dataset** - close the warning and choose a Dataset whose category aligns with the Element. Use this when the Element's category is correct and the mismatched Dataset isn't the right fit.
If you cannot find a Dataset for an Element, check that the Element's taxonomy category is correct. A more general category may give you access to more Datasets.
## Available databases
Variable provides access to several environmental impact databases:
| Database | Coverage | Description |
| :- | :- | :- |
| **ecoinvent** (3.8, 3.11) | Global | Thousands of datasets covering materials, energy, transport, and processes (premium) |
| **DEFRA 2022** | UK | UK government greenhouse gas reporting conversion factors |
| **Ember** (2022, 2024, 2025) | Global | Annual electricity impact data by country and grid, from Ember's Global Electricity Review |
| **US EPA eGRID 2022** | US | US electricity grid impact data, from the EPA's Emissions & Generation Resource Integrated Database |
| **AIB** | Europe | European Residual Mix factors from the Association of Issuing Bodies |
| **Idemat 2023** | Global | Material environmental impact data from Delft University of Technology |
| **IPCC AR6** | Global | Global warming potentials from the IPCC Sixth Assessment Report (Working Group I) |
| **WIOD** | Global | Spend-based, industry-average impact data from the World Input-Output Database |
Database availability depends on your subscription. Get in touch at [support@variable.co](mailto:support@variable.co) if you need additional databases.
## Tips for choosing datasets
A dataset for steel produced in Germany is more accurate for German steel than a global average. Choose datasets that match your supply chain geography.
Different production methods have different impacts. Primary aluminium has much higher emissions than recycled aluminium.
Emission factors change over time as energy grids decarbonize and production methods improve. Recent datasets are generally more accurate.
If your supplier provides their own emission data (via EPD or direct sharing), use that instead of generic database values.
The reference databases don't cover every material or process - if you can't find an exact match, pick the closest proxy and lean toward a **more carbon-intensive** option so you don't understate the impact. Document your rationale (in the Element's notes or in your LCA report) so a third-party verifier can see why you made the choice. If you need something more specific, you can also model the missing item from its own inputs rather than relying on a single proxy Dataset.
## What's next
You now have a complete life-cycle model of your product. To track real-world flows — what you actually buy, sell, and consume — across a reporting period, move on to Activities.
Record purchases, sales, and consumption to build a corporate carbon account
# Creating your first Product (LCA)
Source: https://docs.variable.global/docs/getting-started/create-first-product
A Product (LCA) in Variable is the life-cycle model of a product — what you'll use to understand the environmental footprint of that product, compare design choices, publish an Environmental Product Declaration (EPD), or track impact over time. This guide walks you through creating your first one and getting oriented in the modeling interface.
## Before you begin
Make sure you have access to a Variable account. If you don't have one yet, you can [join the waitlist](https://app.variable.global/waitlist).
## Create a new Product (LCA)
From the main navigation, go to **Products > LCAs**. This is where all your Product (LCA)s live.
Click the **New product** button in the top right corner of the page.
Enter a name for the Product (LCA) and press enter.
You can click on the gear icon in the top right to enter additional metadata like SKU, declared unit, and description. This information helps you keep your products organized and provides context for each Product (LCA).
Choose the category that best describes your product from the taxonomy tree. The taxonomy keeps your products organized and helps surface relevant comparisons across your portfolio.
Variable saves your work automatically as you make changes. You don't need to click a save button.
## Understanding the Product (LCA) page
Once you create a Product (LCA), you will see its page with several key areas:
### Product (LCA) header
At the top of the page, you'll see the name of the Product (LCA) and key metadata. You can edit this information at any time by clicking the gear icon.
#### Impact summary
The impact summary shows the total carbon footprint of the product. Initially this will be `--.--` because you haven't added any inputs yet.
The impact summary displays:
* **GWP-fossil**: The total global warming potential in kgCO2e
* **Per unit**: The carbon footprint per functional unit
* **Breakdown by stage**: A visual breakdown grouped into upstream (A1-A2), direct (A3), and downstream (A4-C4)
### Life-cycle stage sections
The main area of the page is organized by life-cycle stages. Each section represents a different part of your product's life:
| Section | Life-cycle stages | Description | What to model here |
| :- | :- | :- | :- |
| Cradle to gate | A1-A3 | Materials, transport, manufacturing | Inputs to the Product (LCA), factory energy, transport to and within the factory |
| Distribution | A4-A5 | Transport to customer, installation | Shipping to customers, assembly at site |
| Use | B1-B7 | Product in use | Energy consumption during use, maintenance |
| End of life | C1-C4 | Disposal, recycling | Waste processing, recycling processes |
| Optional benefits | D | Beyond system boundary | Credits from recycling, energy recovery |
For a basic Product Carbon Footprint (PCF), you typically focus on A1-A3 stages. This covers "cradle to gate" impacts.
## What's next
Now that you've created a Product (LCA), it's time to add inputs — materials, transport, and manufacturing processes.
Learn how to add inputs and build out the life-cycle model of your product
# Tracking activities
Source: https://docs.variable.global/docs/getting-started/track-activities
While a Product (LCA) models the impact of one of the things you make, **Activities** record the actual flow of materials, energy, and goods through your business — what you bought, what you sold, what you consumed. Activities are how you track Scope 1, 2, and 3 greenhouse gas (GHG) emissions and roll them up into a corporate carbon account.
**Product (LCA) vs. Activity.** A Product (LCA) answers *what is the per-unit footprint of this thing?* using a model of its inputs. An Activity answers *how much did we actually buy or sell, and over what period?* - a quantity of an Element over a date range, with a GHG scope and (for Downstream Activities) a customer. The same Element can appear in many Activities, and the Activity's impact is its quantity multiplied by the Element's per-unit impact data.
## When to use Activities
Use Activities when you want to track real-world flows over a reporting period:
| Example | Direction | Scope |
| :- | :- | :- |
| 50,000 kWh of grid electricity consumed in March | Upstream | Scope 2: Electricity |
| 12 t of stainless steel purchased from a supplier | Upstream | Scope 3: Purchased goods and services |
| 800 km of business travel by car | Upstream | Scope 3: Business travel |
| 2,400 chairs sold to a customer | Downstream | Scope 3: Use of sold products |
The aggregate of your Activities is what feeds your **carbon account** in Reports & plans.
## Anatomy of an Activity
| Field | Description |
| :- | :- |
| **Description** | Short label, e.g. "Steel purchased - Q1" |
| **Direction** | `Upstream` (something flowing in - a purchase, consumption, or input) or `Downstream` (something flowing out - a sale or other output) |
| **Product / Element** | The Material, Energy, Process, or finished Product (LCA) the Activity is about |
| **Quantity** + **Unit** | How much (e.g., 50,000 kWh, 12 t, 800 km) |
| **Start / end date** | The reporting period the Activity covers |
| **Customer** | Counterparty for `Downstream` Activities (set on the Activity) |
| **Org** | The org unit responsible (e.g., Operations, Procurement, Manufacturing) |
| **Scope** | GHG Protocol scope and category, e.g. `Scope 1: Stationary Combustion`, `Scope 2: Electricity`, `Scope 3: Purchased goods and services` |
| **State** | `Draft`, `Pending`, `Approved`, or `Rejected` |
**Supplier and Taxonomy come from the Element**, not the Activity. When you pick the Element, its supplier and taxonomy category are inherited by the Activity automatically - change the Element's supplier or category and every Activity that points to it picks up the change. To change the supplier or taxonomy for a single Activity, swap to a different Element instead.
The calculated CO2e is the Activity's quantity multiplied by the per-unit impact data on the selected Element's Dataset, the same way an Input's impact is calculated inside a Product (LCA).
## Creating an Activity
Go to **Activities** and click the **+ Activity** button. A new draft Activity appears.
New Activities default to **Upstream** - something flowing in (a purchase, consumption, or input). Switch to **Downstream** if you're recording something flowing out (a sale). The direction influences which scopes are available and whether the Activity carries a customer.
Click **Select** to open the Element picker. Search your Inventory for the Material, Energy, or Process this Activity refers to (e.g., *Electricity - Germany grid*, *Hot-rolled steel sheet*). If you haven't created the Element yet, you can create one or pick a Dataset from the **Database** tab and Variable will create the Element for you.
The Element you pick is the same kind of Element used in Product (LCA)s. If you've already modeled a steel Material for your products, re-use it here - both the Activity and any Inputs in a Product (LCA) that reference it will share the same impact data.
Fill in the **Quantity** and **Unit** (e.g., `50000 kWh`, `12 t`, `800 km`). Set the **Start date** and **End date** to the reporting period the Activity covers. If you opened Activities from a data request, the dates pre-fill from the request.
For `Downstream` Activities, pick the **Customer**. Assign the responsible **Org** and confirm the **GHG Scope**.
Variable auto-categorizes **Scope** and **Org** based on the Element you picked and your previous Activities - a grid-electricity Element typically lands as `Scope 2: Electricity`, and Activities that look like prior ones inherit the same Org. You can always override the suggestions; future categorization learns from your overrides.
Click **Submit** to move the Activity from `Draft` to `Pending`. Once an approver confirms it, the state moves to `Approved` and the Activity's emissions are added to your carbon account.
**Approvals roll up the Org structure.** An Activity is approved by a user assigned to an Org unit **above the submitter's own Org unit** - so if you're submitting from a site-level Org unit, your regional or company-level lead approves it, not a peer at the same level. This separation of duties keeps the data trustworthy and is what most third-party verifiers will look for.
Only **Approved** Activities count toward your carbon account totals. Drafts and pending Activities are hidden from rollups so unverified data doesn't pollute your reports.
## Bulk import
For periodic reporting (utility bills, ERP exports, travel data), it's often faster to import Activities in bulk than to create them one by one. From the Activities page, click the import button next to **+ Activity** and upload a CSV or XLSX. Variable maps your columns to Activity fields and creates Drafts you can review and approve.
Imported Activities often arrive without an Element, and they count as zero until they have one. [Map them in bulk](/docs/help/bulk-map-activities) rather than one at a time.
## Tips
Don't create one Activity per invoice - aggregate by reporting period (month, quarter, year). One "March electricity - Berlin office" Activity is easier to manage than thirty daily ones, and the impact math is identical.
A grid-electricity Activity for a German factory should point to a German grid Element (or a regional one), not a global average. The closer the geographic match, the more accurate the carbon account.
Approvals follow the Org structure. Putting Activities under the right Org unit means the right person reviews them - and lets you slice your carbon account by site, region, or business unit.
The Element behind the steel Input in the Product (LCA) of your chair is the same Element that should back your "steel purchased in Q1" Activity. Re-using Elements keeps impact data consistent across product-level and corporate-level reporting.
## What's next
Once you have Activities flowing in, build out reports and reduction plans on top of them.
See your whole company's footprint in one place — sliced by scope, org, or supplier — then plan how to shrink it
# Welcome to Variable
Source: https://docs.variable.global/docs/getting-started/welcome
Variable is a Life-cycle assessment (LCA) platform that helps you calculate and track the environmental impact of your products. Whether you want to publish Environmental Product Declarations (EPDs), measure your product carbon footprint (PCF), or cut emissions across your supply chain, Variable provides the tools you need.
## What you can do with Variable
Build detailed life-cycle assessment (LCA) models for your products
Monitor emissions across your company's value chain and find opportunities to reduce them
## Core terminology
These are the building blocks of a model in Variable — how Products, Inputs, Elements, and Datasets fit together.
| Term | Definition |
| :- | :- |
| **Product (LCA)** | The life-cycle model of a product. The total impact of the product is calculated from its Inputs. |
| **Input** | A row in a Product (LCA). Carries a quantity and unit, and points to an Element. The Input's calculated impact is its quantity multiplied by the per-unit impact data from the Element's Dataset. |
| **Element** | The named source that an Input points to. Like Products, Elements can have their own model — breaking them down into sub-Inputs. Elements come in four kinds — **Material**, **Energy**, **Transport**, and **Process** — and each Element is connected to a Dataset that supplies its per-unit impact data. Elements live in your Inventory and can be referenced by Inputs across many Products. |
| **Inventory** | Your collection of Elements (Materials, Energy, Transport, and Processes) that you re-use across your Product (LCA)s. |
| **Material** | A kind of Element representing a sourced material or component (steel, plastic pellets, a pre-formed housing). Carries metadata like supplier, SKU, and location. |
| **Energy** | A kind of Element representing energy used in your processes, such as electricity or fuel. |
| **Transport** | A kind of Element representing a transport lane from origin to destination. A lane is made up of one or more legs, each with its own mode (truck, rail, ship, air) and distance. An Input that points to a Transport Element only needs the weight of material being moved - Variable derives the rest from the lane's legs. |
| **Process** | A kind of Element representing a manufacturing or processing activity. |
| **Dataset** | The original source of environmental impact data — per-unit impact values for one or more indicators (GWP, acidification, water use, etc.). Often called *emission factors* when the focus is on carbon. Connects to an Element to supply its impact values. Datasets come from databases like ecoinvent, DEFRA, or Ember, and you can import your own. |
| **Activity** | A quantity record of a flow through your value chain over a defined period, classified by org unit and GHG scope (1/2/3). Activities are the basis for corporate carbon accounting and reduction planning. |
## LCA acronyms
Standard LCA acronyms you'll encounter in EPDs, PCRs, and impact reports.
| Term | Definition |
| :- | :- |
| **LCA** | **Life-cycle assessment**. The process of evaluating the environmental impacts of a product throughout its life-cycle. |
| **GWP** | **Global Warming Potential**, measured in kgCO2e (kilograms of carbon dioxide equivalent). |
| **PCF** | **Product Carbon Footprint**. Typically covers life-cycle stages A1-A3 (cradle to gate). |
| **EPD** | **Environmental Product Declaration**. A standardized document describing environmental impacts. |
| **PCR** | **Product Category Rules**. The rules that define how to calculate an EPD for a specific product category. |
**Variable's main indicator is GWP-fossil**. Premium accounts get access to many other indicators including eutrophication, water use, and more.
## Navigating Variable
The main areas of the application are organized to match your workflow:
| Section | What you will find |
| :- | :- |
| **Products > LCAs** | Your Product (LCA)s and the environmental impacts calculated for them |
| **Products > Declarations** | Generate EPDs and other environmental product declarations |
| **Inventory > Elements > Materials** | Materials in your inventory with assigned datasets |
| **Inventory > Elements > Energy** | Energy sources used in your processes |
| **Inventory > Elements > Transport** | Transportation modes and logistics |
| **Inventory > Elements > Process** | Manufacturing and processing activities |
| **Inventory > Datasets** | Datasets from ecoinvent, DEFRA, etc., plus your custom datasets |
| **Activities** | Quantity records of flows through your value chain over time, classified by org unit and GHG scope (1/2/3). The basis for corporate carbon accounting. |
| **Reports & plans** | Analyze activity data, create carbon accounts, and build reduction plans |
| **Data import** | Upload bills of materials and bulk-import inventory data |
## For developers
If you'd like to work with Variable programmatically, you'll need an API User with an API Key - account admins can manage these in [users](https://app.variable.global/users).
* [REST API reference](/api-reference/welcome) - endpoints for Products, Materials, Models, and Activities
* [Postman collection](https://www.postman.com/variable-global/variable-api/overview) - try out the API
* [Data model](/docs/data-model) - how the pieces fit together
## Next steps
Ready to calculate the carbon footprint of your first product? Start by creating a Product (LCA).
Learn how to set up a new Product (LCA) and understand the modeling interface
# Assigning a supplier in bulk
Source: https://docs.variable.global/docs/help/assign-supplier
Set the supplier on many inventory items at once, filling in the ones that are missing or explicitly overwriting what is already there
A material's **supplier** is the company you source it from. It drives inbound transport (A2 and A4) and lets Variable attribute emissions per supplier, so it's worth filling in — but setting it one item at a time, in [each item's settings](#setting-a-supplier-on-one-item), is slow work once you have a few hundred materials.
**Assign supplier** does it in bulk. Select items in your inventory, pick a supplier, and Variable points all of them at that company in one action.
## Running it
In **Inventory → Elements**, tick the items you want. The bulk toolbar slides in from the bottom with the selection count and its actions.
Pick **Assign supplier** from the toolbar. A dialog opens with a supplier picker — search your existing suppliers, or create a new one from the same field.
Leave **Overwrite existing suppliers** off to fill in only the items that are missing one, or tick it to re-point every selected item. See [the two modes](#the-two-modes) below.
The dialog shows how many items will be assigned and how many will be skipped before you commit. The confirm button repeats the number — *"Assign to 42 items"* — so you always know the size of the write.
## The two modes
The dialog uses the same idea as the [import match mode](/docs/import/import-behavior#import-match-mode-products-and-materials): fill in what's missing, or let your new value win.
| Mode | What happens |
| :- | :- |
| **Fill missing only** (default — the checkbox off) | Only items with no supplier are assigned. An item counts as missing when its supplier is unset, or when it points at your own company — that means *made in-house*, not *sourced from someone*. Items that already name another company are left alone and reported as **skipped**. |
| **Overwrite existing** (the checkbox on) | Every selected item is re-pointed at the chosen supplier, including items that already have one. Nothing is skipped. |
Fill-missing is the default on purpose: it's the safe pass. Run it across your whole inventory as often as you like — it never changes a supplier you've already recorded, and the skipped count tells you how many items it deliberately left alone.
## Overwriting, and why materials are supplier-specific
In Variable a material is tied to a specific supplier, because the same substance from two suppliers can carry very different emissions — different processes, energy mixes, and geography. So changing a material's supplier is normally *not* how you switch sourcing: to source low-alloyed steel from a new mill, create a new material for that mill and swap it into the [bill of materials](/docs/getting-started/add-materials), keeping each supplier's footprint honest and your history intact.
That makes **Overwrite existing** a correction, not a sourcing change. Reach for it when the supplier on record is simply wrong or was recorded against a placeholder company — you're fixing the label on a material that always came from this supplier.
Supplier feeds A2/A4 transport and per-supplier attribution, so an overwrite can move impact results across every selected item at once. Each change is recorded in the item's history, but there's no one-click undo for a bulk assignment — correcting one means assigning the right supplier again. Check the counts before you confirm.
This is also the one place the supplier link changes in bulk. [Data import](/docs/import/import-behavior) never touches relationships, so re-importing a file won't move a material to a different supplier no matter which match mode you choose.
## What can and can't be assigned
Bulk assignment writes to **drafts** only, and only to items your account owns.
* An item in any state but **Draft** — Locked, In review, Rejected or Verified — is reported back as *"is not in draft state"* and left unchanged. To edit it, create a new draft version — see [Product and material versioning](/docs/help/versioning).
* The supplier you pick has to be a company your account can use. Creating one from the picker takes care of that for you.
## Setting a supplier on one item
The bulk action is a shortcut for a field that lives on every item. Open the item, click the gear icon in the top right, and find the **Product / part** selector — it decides whether there's a supplier to set at all.
* **Product** means your company makes it, so there's no supplier to name. This is the *made in-house* case that fill-missing treats as missing.
* **Part** means you purchase it, or manufacture it in-house for your own use. The **Supplier** picker sits beside the selector — search your suppliers or create one from the field, the same as in the bulk dialog.
* On a dataset the selector reads **My company / Supplier** instead, with the same effect.
You can also set the supplier while creating an Element, in the new-Element dialog.
Transport items carry no supplier of their own. A transport input takes the supplier of the material it moves, and a transport mode names the organization that published its dataset rather than a company you source from.
## Related topics
* [Supplier locations](/docs/help/supplier-locations) — give a supplier named addresses, then use them as transport origins
* [Auto-assigning transport](/docs/help/auto-assign-transport) — fills in A2/A4 from a supplier's location once the supplier is set
* [Import match mode](/docs/import/import-behavior#import-match-mode-products-and-materials) — the same fill-missing/overwrite choice on the import side
* [Supplier API](/api-reference/v1/supplier/list/get) — manage suppliers through the public API and MCP
# Mapping Activities to an Element in bulk
Source: https://docs.variable.global/docs/help/bulk-map-activities
Find the Activities that have no Element and map a whole selection to one Element in a single action
An Activity's impact is its quantity multiplied by its Element's per-unit impact data, so an Activity with no Element counts as zero — and nothing on the page tells you it's missing. Imports, ERP syncs and the API bring Activities in by the hundred, and setting the Element one Activity at a time doesn't scale. Bulk mapping finds the Activities with no Element and points a whole selection at one Element in a single action.
## Finding Activities with no Element
On **Activities**, open the **Element** filter and pick **No element**, or click **Show activities with no element** beside the activity count. The list then holds only Activities with neither an Element nor a transport lane — a freight Activity is mapped through its lane, so it never shows up as missing an Element.
## Mapping a selection
Only Draft and Rejected Activities can be mapped, so the **Element** action and the **Show activities with no element** shortcut appear on those two tabs, not on the default **Approved** tab.
Tick rows, or click **Select all** in the bulk toolbar to take every Activity matching the current filters, not just the page you can see.
Click **Element** in the toolbar and search your inventory. If the list is filtered by unit type, category or supplier, the search starts with the same filters.
The dialog names the Element, the unit it's declared in, and how many Activities will be mapped. The confirm button repeats the number — *"Map 240 Activities"* — so you always know the size of the change.
The mapping runs in the background and recalculates each Activity as it goes. When it finishes, a message reports how many Activities were mapped, and any that weren't are listed above the table with the reason. One mapping takes up to 10,000 Activities; narrow the filter to map more.
There's no undo for a bulk mapping — correcting one means mapping the right Element again. Check the Element and the count before you confirm.
## Why an Activity isn't mapped
An Activity that can't take the Element is left exactly as it was. The list above the table shows the first ten with their reasons, and **Select these activities** reselects all of them so you can fix the cause and map again.
| Reason | What it means |
| :- | :- |
| **Activity is pending or approved** | A Pending Activity is waiting on an approver and an Approved one already counts toward your carbon account, so a bulk mapping never changes either — the same rule as editing a single Activity. |
| **Unit cannot convert to the Element's unit** | The Activity's unit and the Element's declared unit measure different things — litres against kilograms, say — and the Element states no weight, volume, length or area per declared unit that bridges them. Pick an Element declared in the same kind of unit, or add the missing amount to the Element. |
| **Element is not available to this Activity** | An Upstream Activity takes any Element in your inventory. A Downstream Activity, or one that belongs to a data request, takes only Elements of the supplier it already names — or your own company's, when it names none. |
| **Activity not found** | It was deleted, or is no longer in your account, after you selected it. |
## The supplier follows the Element
An Activity's supplier comes from its Element, so mapping an Upstream Activity also updates its supplier: map it to an Element another company supplies and that company becomes the supplier, or to one of your own Elements or a Dataset and the supplier is cleared. That's the same thing that happens when you pick the Element on a single Activity. Downstream and data-request Activities keep the supplier they have.
## Related topics
* [Tracking activities](/docs/getting-started/track-activities) — what an Activity is and how its impact is calculated
* [Assigning a supplier in bulk](/docs/help/assign-supplier) — the same kind of bulk action for Elements in your inventory
# Documenting an element
Source: https://docs.variable.global/docs/help/element-documentation
Attach supporting files — with a comment on each — to any product, model, material, dataset, or electricity element
Every element has a **Documentation** section for supporting files: the rationale behind a dataset choice, what else you considered, a supplier's declaration, a lab report. Anything that explains a modeling decision but isn't itself a Product sheet.
Before this existed the Product sheet slot was the only general-purpose place to put such a file, and datasets never saw it at all. Documentation is separate from both, and every element type has it.
## Adding a file
From an element's page, click the **Settings** gear in the header.
It's under **Product info** for products, models, sub-models, materials, and electricity, and under **Dataset info** for datasets — in both cases below the element's own fields.
Click **Add documentation**, or drag a file onto it. There's no limit on how many you attach.
Each row has a comment field — *"Add a comment"* — next to the filename. Type up to 1,000 characters; it saves as you go, no separate save step.
Accepted file types are `pdf`, `doc`, `docx`, `xls`, `xlsx` and `csv`, plus images (`png`, `jpg`, `svg`, `webp`, `avif`, `heif`, `heic`). Each file can be up to 50 MB. Click a filename to preview it; hover a row and use the delete icon to remove it.
## Who can see it
**Documentation stays inside your company.** Unlike a product image or a Product sheet, a documentation file does not inherit the element's visibility — publishing a product, sharing it by link, or exposing it over the API does not expose its documentation. It's the same expectation as the **Notes (for internal use)** field it sits above.
That also means the documentation you see on an element is your own company's. A supplier attaching their reasoning to an element they share with you does not send it to you along with the element.
## Where it helps
Verification is the usual reason. A verifier reviewing your model wants to know why a particular dataset was chosen and what the alternatives were — attaching that reasoning to the element itself keeps it with the model instead of in a side spreadsheet that has to be found again next cycle.
# Environmental impact indicators
Source: https://docs.variable.global/docs/help/impact-indicators
Understanding EN 15804+A2 environmental impact categories and indicators
Variable tracks environmental impacts using the **EN 15804+A2** standard, which defines a comprehensive set of indicators beyond just carbon emissions. These indicators provide a complete picture of a product's environmental profile.
## GWP-fossil: Variable's default indicator
**Variable's default headline indicator is GWP-fossil** - Global Warming Potential from fossil-fuel-derived emissions, measured over a 100-year time horizon and expressed in kilograms of CO2 equivalent (kg CO2e).
EN 15804+A2 splits GWP100 into four climate-change categories. Variable defaults to **GWP-fossil** so biogenic uptake/release and land-use-change effects don't mask the fossil-fuel signal - you can set any other indicator as the **Selected indicator** in the modeling screen for side-by-side comparison.
| Indicator | Description |
| :- | :- |
| GWP-fossil | **Default.** Emissions from fossil-fuel combustion and fossil-derived processes |
| GWP-total | Sum of all GWP contributions (fossil + biogenic + luluc) |
| GWP-biogenic | Net emissions from biogenic carbon (uptake and release) |
| GWP-luluc | Emissions from land use and land-use change |
## Core environmental indicators
These 13 indicators cover the fundamental environmental impact categories:
| Indicator | Unit | Description |
| :- | :- | :- |
| GWP-total | kg CO2e | Total climate change impact |
| GWP-fossil | kg CO2e | Fossil fuel-related climate impact |
| GWP-biogenic | kg CO2e | Biogenic climate impact |
| GWP-luluc | kg CO2e | Land use change climate impact |
| ODP | kg CFC-11 eq | Ozone depletion potential |
| AP | mol H+ eq | Acidification potential (acid rain) |
| EP-freshwater | kg P eq | Freshwater eutrophication |
| EP-marine | kg N eq | Marine eutrophication |
| EP-terrestrial | mol N eq | Terrestrial eutrophication |
| POCP | kg NMVOC eq | Photochemical ozone creation (smog) |
| ADPE | kg Sb eq | Abiotic depletion - minerals and metals |
| ADPF | MJ, net calorific value | Abiotic depletion - fossil resources |
| WDP | m3 world eq | Water deprivation potential |
## Resource use indicators
These 10 indicators track energy and material consumption:
| Indicator | Unit | Description |
| :- | :- | :- |
| PERE | MJ | Renewable primary energy (excluding raw materials) |
| PERM | MJ | Renewable primary energy used as raw materials |
| PERT | MJ | Total renewable primary energy |
| PENRE | MJ | Non-renewable primary energy (excluding raw materials) |
| PENRM | MJ | Non-renewable primary energy used as raw materials |
| PENRT | MJ | Total non-renewable primary energy |
| SM | kg | Secondary material use |
| RSF | MJ | Renewable secondary fuels |
| NRSF | MJ | Non-renewable secondary fuels |
| FW | m3 | Net use of fresh water |
The PERM/PENRM indicators track energy content in materials (like the energy stored in plastics or wood). Variable applies special rules to track this energy as it enters and exits the product system. See [Life-cycle stages explained](/docs/help/life-cycle-stages) for details on material energy accounting.
## Waste indicators
| Indicator | Unit | Description |
| :- | :- | :- |
| HWD | kg | Hazardous waste disposed |
| NHWD | kg | Non-hazardous waste disposed |
| RWD | kg | Radioactive waste disposed |
| HLRW | kg | High-level radioactive waste disposed |
## Biogenic carbon indicators
These indicators track the carbon content stored in products and packaging:
| Indicator | Unit | Description |
| :- | :- | :- |
| BCCP | kg C | Biogenic carbon content in product |
| BCCAP | kg C | Biogenic carbon content in packaging |
BCCP and BCCAP represent carbon storage, not emissions. They only apply to the A1-A3 (product stage). A material line counts toward BCCP when its material group is Product, toward BCCAP when it is Packaging, and toward neither when it is Other (ancillary). A line with no group takes its stage's default: Product at A1, Other (ancillary) at A2 and Packaging at A3. Waste, process, energy and transport lines count toward neither, whatever group they store, because their matter does not become part of the product.
## Output flow indicators
These indicators track materials and energy leaving the product system:
| Indicator | Unit | Description |
| :- | :- | :- |
| CRU | kg | Components for reuse |
| MFR | kg | Materials for recycling |
| MER | kg | Materials for energy recovery |
| EE | MJ | Total exported energy |
| EEE | MJ | Exported electrical energy |
| EET | MJ | Exported thermal energy |
## Additional indicators
Seven additional indicators for human and ecosystem toxicity:
| Indicator | Unit | Description |
| :- | :- | :- |
| GWP-GHG | kg CO2e | Climate change (excluding biogenic uptake/release) |
| PM | disease incidence | Particulate matter formation |
| IRP | kBq U-235 eq | Ionising radiation |
| ETP-fw | CTUe | Freshwater ecotoxicity |
| HTP-c | CTUh | Human toxicity - carcinogenic |
| HTP-nc | CTUh | Human toxicity - non-carcinogenic |
| SQP | dimensionless | Land use / soil quality |
# Life-cycle stages explained
Source: https://docs.variable.global/docs/help/life-cycle-stages
Understanding EN 15804+A2 life-cycle stages
Life-cycle assessment (LCA) breaks down a product's environmental impacts across distinct stages, from upstream material production through end-of-life. Variable follows the **EN 15804+A2** standard, which defines a comprehensive framework for these stages.
EN 15804 calls each A1-D stage an **information module**. We use **life-cycle stage** throughout Variable - the two terms mean the same thing.
## Stage overview
EN 15804+A2 organizes the product life-cycle into four main phases:
```mermaid theme={"system"}
flowchart TB
subgraph CradleToGate["Cradle to gate (A1-A3)"]
subgraph Upstream
A1[A1: Materials]
A2[A2: Transport to factory]
end
subgraph Direct
A3[A3: Manufacturing]
end
end
subgraph Downstream
subgraph Distribution["Distribution (A4-A5)"]
A4[A4: Distribution]
A5[A5: Installation]
end
subgraph UsePhase["Use (B1-B7)"]
B[B1-B7: Use phase]
end
subgraph EndOfLife["End of life (C1-C4)"]
C[C1-C4: End of life]
end
end
subgraph Optional["Optional benefits (D)"]
D[D: Beyond system]
end
A1 --> A2 --> A3 --> A4 --> A5 --> B --> C --> D
style A1 fill:#d1edff,stroke:#d1edff,color:#003540
style A2 fill:#d1edff,stroke:#d1edff,color:#003540
style A3 fill:#d1edff,stroke:#d1edff,color:#003540
style A4 fill:#ede5ff,stroke:#ede5ff,color:#003540
style A5 fill:#ede5ff,stroke:#ede5ff,color:#003540
style B fill:#e8f2a1,stroke:#e8f2a1,color:#003540
style C fill:#f7edcf,stroke:#f7edcf,color:#003540
style D fill:#f3f0ed,stroke:#f3f0ed,color:#003540
style CradleToGate fill:transparent,stroke:#BDB8B2
style Distribution fill:#F8F5F2,stroke:#DDD9D5
style UsePhase fill:#F8F5F2,stroke:#DDD9D5
style EndOfLife fill:#F8F5F2,stroke:#DDD9D5
style Optional fill:transparent,stroke:#BDB8B2
style Upstream fill:#F8F5F2,stroke:#DDD9D5
style Direct fill:#F8F5F2,stroke:#DDD9D5
style Downstream fill:transparent,stroke:#BDB8B2
```
Module **D** (benefits beyond the system boundary) is reported separately and **does not count toward a product's total footprint**. It captures potential benefits - reuse, recycling, energy recovery - that happen outside your system boundary.
## Detailed stage reference
| Stage | Name | Phase | Description |
| :- | :- | :- | :- |
| A1 | Materials | Upstream | The materials and components that go into your product, including their upstream production from cradle to the supplier's gate |
| A2 | Transport to manufacturing | Upstream | Transportation of materials and components to the manufacturing facility |
| A3 | Manufacturing | Direct | All manufacturing and assembly processes, including energy consumption, waste, and packaging |
| A4 | Transport to customer | Downstream | Distribution of the finished product to the customer or construction site |
| A5 | Installation/assembly | Downstream | On-site installation, assembly, or construction activities |
| B1 | Use | Downstream | Emissions during normal product use (e.g., off-gassing, refrigerant leakage) |
| B2 | Maintenance | Downstream | Regular maintenance activities throughout the product's service life |
| B3 | Repair | Downstream | Repair activities to restore product functionality |
| B4 | Replacement | Downstream | Replacement of components that have a shorter lifespan than the product |
| B5 | Refurbishment | Downstream | Major refurbishment or renovation activities |
| B6 | Operational energy use | Downstream | Energy consumed during product operation |
| B7 | Operational water use | Downstream | Water consumed during product operation |
| C1 | Deconstruction/demolition | End of life | Dismantling or demolition of the product at end of service life |
| C2 | Transport to processing | End of life | Transportation of waste materials to processing facilities |
| C3 | Waste processing | End of life | Recycling, recovery, or other waste treatment processes |
| C4 | Final disposal | End of life | Landfilling or other final disposal methods |
| D | Benefits beyond boundary | Optional | Potential benefits from reuse, recycling, and energy recovery outside the system boundary |
## What to include in each stage
These are non-exhaustive examples to get you started. Your product's **PCR (Product Category Rules)** is authoritative - always follow it where it differs from the guidance below.
### A1: Materials
The materials in your model. Each input is a quantity of a Material - whether unprocessed (ore, timber), processed (steel sheet, plastic pellets), or pre-formed (a steel pipe, a molded housing). The Dataset behind each Material covers its upstream production from cradle to the supplier's gate, including mining or harvesting, refining, and any supplier processing. It does **not** include transport to your factory - that's A2.
Packaging can fall into different stages depending on what it is: packaging on incoming supplier materials commonly sits in A1, manufacturing or product packaging in A3, and distribution packaging in A4. When in doubt, default to A1 - but always follow your PCR, which is authoritative on where each kind of packaging belongs.
### A2: Transport to manufacturing
* All transport modes from suppliers to your factory
* Consider supplier location and transport distances
### A3: Manufacturing
* Electricity and fuel consumption
* Direct emissions from a process (e.g., welding, curing, calcination, clinkering)
* On-site waste generation and treatment
* Water consumption and wastewater treatment
### A4-A5: Distribution and installation
* Transport to distribution centers and final customers
* Installation energy and consumables
* Construction site waste
* Temporary works and scaffolding
### B stages: Use phase
* Energy consumed during operation (B6) - typically the largest use-phase contribution
* Maintenance schedules and replacement cycles
### C stages: End of life
* Demolition and dismantling energy
* Transport to waste facilities
* Recycling processes
* Landfill or incineration
## Bundling and unbundling nested models
When a line in your bill of materials points to another product you've modeled in Variable (a *nested* model), you can choose how its stages appear:
* **Bundled** (the default) — the nested model contributes as a single line carrying its cradle to gate (A1-A3) total, wherever the line sits in your model.
* **Unbundled** — the nested model's own stages are broken out and laid out individually, so its detail flows into your product across the matching stages.
Unbundling is available for every stage group: cradle to gate (A1-A3), distribution (A4-A5), use (B1-B7), end of life (C1-C4), and optional benefits (D). For the multi-stage groups this is how you pull a separately-modeled phase — for example a child end-of-life model with its own C1-C4 lines — into a parent product with the stages kept intact. Optional benefits (D) is a single stage: bundled, it imports the source's cradle to gate (A1-A3) total; unbundled, it imports the source's own module D credit (its recycling or recovery benefit) into stage D.
Open a line's actions menu and use the **Bundling** options to switch between bundled and unbundled; each option shows what it imports. Switching changes how the nested model's detail is grouped into your product's stages; it doesn't change what the nested model itself contains.
Through the API, set `bundled` on a model input to make the same choice.
Bundling also decides which stage the nested model's own lines are reported under in your export. Bundled, they are reported at the stage the parent line sits in — a carton model bundled into A1 has its board and its ink listed under A1, matching the single A1 total the calculation books. Lines the parent's stage doesn't cover are left out rather than moved: that same carton's C3 disposal line doesn't appear at all, because the calculation doesn't count it there either. Unbundled, and with no bundled line above it, a line keeps its own stage, because that is how the calculation counts it.
The model page shows the same thing: under a bundled line, a nested row's Stage cell is a read-only chip carrying the stage it inherits, and hovering it names the bundled line the stage came from. Unbundle that line to give the row its own stage back — editing the stage on the nested model's own page won't change what the chip reports while the line above stays bundled.
Bundling also changes how the nested model's weight appears in your materials breakdown. Bundled, it shows as a single line carrying the sub-model's own total. Unbundled, that line carries no weight of its own and the individual materials underneath it carry theirs instead. Either way the total is the same — but don't add the two together, because the sub-model line and its materials describe the same weight.
One case is not yours to choose: when a line sources a model published by another company on Variable, it always appears as a single line. Their bill of materials is theirs, so your export shows that single line rather than breaking it down into their materials. Supplier records you created in your own account aren't affected — those are yours, and their detail flows through normally.
## Examples by product type
### Electronics (laptop)
| Stage | Example inputs |
| :- | :- |
| A1 | Semiconductors, metals, plastics, battery cells |
| A2 | Component shipping from Asia |
| A3 | Assembly, testing, packaging |
| A4 | Air freight to retailers |
| B6 | Electricity during 5-year use phase |
| C3-C4 | E-waste recycling, circuit board processing |
### Construction materials (steel beam)
| Stage | Example inputs |
| :- | :- |
| A1 | Iron ore mining, coking coal |
| A2 | Rail transport to steel mill |
| A3 | Electric arc furnace, rolling |
| A4 | Truck delivery to site |
| A5 | Crane operation, welding |
| C1 | Cutting for demolition |
| C3-C4 | Steel recycling |
### Consumer goods (furniture)
| Stage | Example inputs |
| :- | :- |
| A1 | Wood harvesting, foam production |
| A2 | Truck from suppliers |
| A3 | Cutting, assembly, finishing |
| A4 | Delivery to customer |
| B2 | Cleaning products, reupholstery |
| C3-C4 | Wood recycling or landfill |
# Model CSV export
Source: https://docs.variable.global/docs/help/model-csv-export
Which basis the exported file reports, and how to reconcile it against the model page
**Export CSV** on a product gives you the whole model as one flat file — every bill-of-materials line, nested to any depth, one row each. It's the artifact you hand to a verifier, so it's worth knowing exactly what its numbers measure.
The short version: **the file is scaled to the product, and the model page is scaled to the parent line.** Both surfaces use the same words for those two bases, so a file number that looks wrong is usually the same number on a different basis.
## The file reports one declared unit of the product
Every `Input_Quantity`, `Input_Weight` and `Input_kgCO2e` in the file is what **one declared unit of the product you exported** consumes or emits. A nested row is multiplied through every line above it — each ancestor contributes the number of its own source element's declared units that the line consumes.
The model page does the opposite, deliberately. A row there shows the quantity **as you entered it on its parent**, because a bill-of-materials line is edited in its parent's terms ("this frame takes four legs"). That's the number you need while modeling; the file's is the number you need when reporting.
`Level` tells you which basis a row is on. `Level` 1 is a top-level line of the exported product, and every step deeper adds one. On a `Level` 1 row the two surfaces agree, because there's nothing above it to multiply through.
## A worked example
A factory model takes 6.05 kg of an element that is itself a model, declared per kg. Two of that element's own lines:
| Line | On the model page | In the file | Why |
| :- | :- | :- | :- |
| Water | `1.3 kg` | `7.865` | 1.3 kg per kg of the element, and the factory takes 6.05 of them |
| Electricity | `0.6319 kWh` | two rows summing to `3.823` | The same 6.05 multiplier, shared between its solar and hydro sources |
Every figure here is right. Multiply an `Input_Quantity` on the page by the quantity on each line above it and you land on the file's.
`Input_Weight` is the one column that won't always reconcile that way. The file derives every line's weight from its quantity and its source element's weight, while the model page shows a weight recorded on the line itself whenever there is one. Where those two disagree, the gap is the recorded weight, not the basis.
**Don't total a page figure together with a file figure.** A sum built from both bases means nothing. Pick one surface per calculation.
## Related topics
* [Data quality rating](/docs/help/data-quality#in-a-csv-export) — the DQR columns the file carries
* [Versioning](/docs/help/versioning#versions-in-a-csv-export) — which version each row reports
* [Life-cycle stages](/docs/help/life-cycle-stages) — which stage a nested line is reported under
# Saved views
Source: https://docs.variable.global/docs/help/saved-views
Save a table filter, sort and column layout as a named view, for yourself or your whole company
A saved view remembers how you set a table up so you do not rebuild it every time. A view is yours alone until you share it with your company, and the address bar carries whatever you are looking at, so a copied link opens the same table for a colleague.
Saved views are available on the LCA groups list and the locations list. Each list keeps its own set — a view saved on Locations appears on Locations.
## What a view remembers
| Part | What it captures |
| :- | :- |
| Columns | Which columns are shown, and the order they appear in |
| Filter | Every condition in the filter, including the quick search |
| Sort | The column the table is ordered by, and the direction |
Nothing else is stored. Column widths, pinned columns and row density are remembered per browser rather than on the view, and the page you are on rides in the link.
Changes you have not saved are kept in your browser too, for the table and the view you are on, so leaving the page or refreshing does not throw them away — the dot stays on the tab until you **Save**, **Reset**, or switch to another view, which takes the slot for itself. A link still wins: open one and you see the filter and sort it carries rather than your own unsaved ones.
## Build and save a view
1. Open the LCA groups list, or the locations list. The tabs above the table list every view, starting on **All**, which is the layout the app ships with.
2. Narrow the rows. Type in the filter bar for a quick match across the table, and press Enter to keep it as a chip; typing the value of a column such as PCR also offers that value as a filter. Use the funnel on a column header for one field, or the funnel at the start of the filter bar for several conditions at once. Each condition shows as a chip in the bar, and the footer counts the rows that match.
3. Click a column header to sort by it. Click again to reverse the direction.
4. Reorder or hide columns from the **Display** menu. Row height sits in the same menu but stays with your browser rather than joining the view.
5. As soon as anything differs from the view you are on, a dot appears on its tab and **Reset** and **Save as new view** appear after the tabs. Click **Save as new view**, name the view, turn on **Share with everyone at \** if your colleagues should see it, and click **Create view**.
The new view is selected straight away. A personal view appears as a tab for you alone; a shared one appears for everyone in your company. Saving is offered only once something differs from the view you are on — there is nothing to save while the table matches it.
## Change or discard a view
Adjust a saved view the same way you built it, then choose:
* **Save** writes your changes onto the view.
* **Save as new view** leaves the original alone and creates a second view from what is on screen.
* **Reset** throws your changes away and puts the view back the way it was saved.
To delete a view, select its tab and open the menu beside it, then choose **Delete** and confirm. A shared view goes for everyone in your company, and the table falls back to the default view.
**Save** and **Delete** are offered to whoever created the view, and to owners and admins. Anyone else sees **Save as new view** and **Reset** only, so a shared view cannot be rewritten out from under the team that relies on it. The default view is never saved over, so it has no **Save**.
## Share a view with your company
A view starts personal. Turn on **Share with everyone at \** when you create it, or later select its tab, open the menu beside it and use the switch on the same setting. Turning it back off makes the view personal again; colleagues who had it open fall back to the default view. That menu is the one **Delete** lives in, so sharing and stop-sharing are offered to whoever created the view, and to owners and admins.
## Share what you are looking at
Copy the address bar and send it. The link carries the view you are on plus any filter or sort layered on top, so the recipient sees the same rows in the same order even if you never saved. Two exceptions: a personal view opens as the default view for anyone but you, so share it first; and hiding or reordering a column only travels once you save it onto a view.
A link to a view that has since been deleted falls back to the default view rather than showing an error.
# Impacts
Source: https://docs.variable.global/docs/impacts
Every product, material, dataset, switch, and database search result can return its full set of
**EN 15804+A2 environmental impact indicators** — not just carbon. Impacts come back under a
top-level `impacts` field, keyed by the indicator's canonical code (for example `GWP-fossil`,
`ODP`, `AP`). You choose which indicators to include with the `impacts` query parameter.
For the full list of indicators and what each one measures, see
[Environmental impact indicators](/docs/help/impact-indicators).
## The `impacts` field
`impacts` is an object keyed by indicator code. Each value carries the indicator's `unit` and
`method`, plus its values for the individual life-cycle stages (`A1`, `A2`, `A3`, …) and the
aggregated totals (`totalCarbonFootprint`, `A1_A3`, …) the indicator covers.
```json theme={"system"}
{
"uuid": "123e4567-e89b-12d3-a456-426614174000",
"name": "Steel billet, hot rolled",
"impacts": {
"GWP-fossil": {
"unit": "kgCO2e",
"method": "Mixed",
"totalCarbonFootprint": "10",
"A1_A3": "10"
},
"ODP": {
"unit": "kgCFC11e",
"method": "EN15804+A2 - Core impact categories and indicators",
"A1_A3": "0.0000012"
},
"AP": {
"unit": "molH+e",
"method": "EN15804+A2 - Core impact categories and indicators",
"A1_A3": "2"
}
}
}
```
Values are strings. One that is genuinely zero is `"0"`; one with no value is absent.
The aggregated stage `A1_A3` is returned alongside the individual `A1`, `A2`, and `A3` stages, so
you can read either the combined product stage or its parts. `method` is `Mixed` when the total
aggregates two or more components from different methods — common for the headline `GWP-fossil` total.
## The `impacts` query parameter
Add `?impacts=` to any `GET` that returns these resources to control which indicators come back.
| `?impacts=` value | Returns |
| :- | :- |
| absent or empty | Only `GWP-fossil` |
| `all` | Every indicator |
| `gwp-fossil,odp,ap` | Just those indicators (codes are case-insensitive) |
An unrecognized code returns `400` with the list of valid codes. `all` is only honored on its own —
`?impacts=all,gwp-fossil` is treated as a request for an unknown code and returns `400`.
```bash theme={"system"}
curl https://app.variable.global/v1/dataset/{uuid}?impacts=all \
-H "Authorization: Bearer $TOKEN"
```
`GWP-fossil` is always available. Access to the full indicator set may depend on your account.
## Deprecation: `footprint.CO2e`
The existing `footprint.CO2e` object is unchanged and keeps working, but it is **deprecated** in
favor of `impacts["GWP-fossil"]`, which carries the same GWP-fossil values. Migrate to `impacts`
when you can; `footprint.CO2e` has no removal date yet.
Endpoints: [Product](/api-reference/v1/product/get), [Material](/api-reference/v1/material/get),
[Dataset](/api-reference/v1/dataset/get), [Switch](/api-reference/v1/switch/get), and
[Database search](/api-reference/v1/search/list).
# Import behavior by type
Source: https://docs.variable.global/docs/import/import-behavior
What each import type creates, how it matches existing records, and when a re-import creates new data vs. reuses what you already have
Every import type follows its own rules for two questions:
1. **What does it create?** - which records appear in your workspace.
2. **When does it create a new record vs. reuse an existing one?** - how a row is matched against data you already have, and what happens when it matches.
Knowing these rules lets you re-run an import without guessing whether you'll end up with duplicates.
## At a glance
| Import type | What it creates | Matched against existing by | Re-importing a matching row |
| :- | :- | :- | :- |
| **Products** | A product owned by your company | SKU, within your company | Match mode decides: reused (default), overwritten, or blanks filled |
| **Materials** | A source product, plus a supplier when supplier columns are present | SKU, within the owning supplier | Match mode decides: blanks filled (default), reused, or overwritten |
| **Bill of materials (BOM)** | Inputs on the target product, plus the supplier parts and materials they reference | Products by SKU + ownership; inputs by source + quantity + unit | Products reused; inputs added only to unmodeled products |
| **Suppliers** | A supplier company, plus contacts when contact columns are present | UUID, then your internal ID, then name | Reused, never overwritten; new contacts merged in |
| **Locations** | A named location, optionally geocoded | Location name, within the owning company | Reused; blank fields filled in, set values kept |
| **Activity data**, **Air travel**, **Transport** | One activity record per row | Not matched | **A new record every time** |
**Products, materials, suppliers, locations, and BOMs are safe to re-run** - matching records are reused or updated in place, so you won't create duplicates. **Activity, air travel, and transport imports are not** - every row becomes a new record each time you import the file. See [Activity data, air travel, and transport](#activity-data-air-travel-and-transport) below.
The sample rows below use Variable's canonical column names. Your file's headers don't need to match - the [column mapping step](/docs/import/upload-map-columns) lets you map any header to the right field.
## Products
Creates a **product owned by your company** for each row.
* **Matched by:** SKU, scoped to your company. If a row has no SKU, the product name is used instead.
* **On a match:** the [import match mode](#import-match-mode-products-and-materials) decides. By default (**Create new only**) the existing product is reused and left untouched. Choose **Overwrite existing** or **Fill missing only** to update its scalar fields - weight, codes, description, notes - from your file; its links (supplier, datasets, model inputs) are never changed.
* **Created new when:** no product matches - by SKU, or by name when the row has no SKU. A SKU-less row gets a SKU generated from its name on creation.
**Sample rows:**
```csv theme={"system"}
name,sku,unitCode,weightQuantity,weightUnitCode
Mountain Bike,BIKE-001,item,15.2,kg
Road Bike,BIKE-002,item,8.4,kg
```
Alongside `weightQuantity`/`weightUnitCode`, product rows can carry `lengthQuantity`/`lengthUnitCode`, `volumeQuantity`/`volumeUnitCode`, and `areaQuantity`/`areaUnitCode`. Map a quantity column without its unit and Variable assumes the SI base unit (m, m3, m2); leave a dimension blank and it stays unset.
## Materials
Creates a **source product** (a material or supplier part) for each row, and a **supplier company** when supplier columns are present.
* **Matched by:** SKU, scoped to the owning supplier. The supplier is matched first by UUID, then by your internal supplier ID, then by supplier name. Name is the fallback when a row has no SKU.
* **On a match:** the [import match mode](#import-match-mode-products-and-materials) decides. By default (**Fill missing only**) blank scalar fields on the existing material are filled in from your file, while values you've already set are kept. Choose **Create new only** to leave matched materials untouched, or **Overwrite existing** to replace their values; the supplier link and any datasets or inputs are never changed in any mode.
* **Created new when:** no material matches - by SKU, or by name when the row has no SKU. A SKU-less row gets a SKU generated from its name on creation.
**Sample rows:**
```csv theme={"system"}
name,sku,supplier,supplierId,unitCode,weightQuantity,weightUnitCode
Aluminum Alloy 6061,ALU-6061,RimSupply,SUP-001,kg,1,kg
Natural Rubber,RUB-NAT,TireCo,SUP-002,kg,1,kg
```
Alongside `weightQuantity`/`weightUnitCode`, material rows can carry `lengthQuantity`/`lengthUnitCode`, `volumeQuantity`/`volumeUnitCode`, and `areaQuantity`/`areaUnitCode`. Map a quantity column without its unit and Variable assumes the SI base unit (m, m3, m2); leave a dimension blank and it stays unset.
## Bill of materials (BOM)
Creates **inputs** (line items) on the product you're importing into, plus any **supplier parts** and **materials** the rows reference. See [What happens after upload](/docs/import/import-materials) for the full 3-level structure.
* **Matched by:** products are matched by SKU and ownership; individual inputs are matched by their source product, quantity, and unit.
* **On a match:** products are reused and left untouched. Inputs are only added to a product that was **created during this import**, is still **UNKNOWN**, or has **no inputs yet**. A product that's already LIVE with inputs is treated as fully modeled and keeps its existing inputs.
* **Created new when:** the product or input isn't already present under those rules.
**Sample rows** (a root product on level 0, with a supplier part and its material on level 1):
```csv theme={"system"}
bomLevel,productSku,productName,quantity,unitCode,supplierName,supplierPartId,materialName,weightAmount,weightUnitCode
0,BIKE-001,Mountain Bike,1,item,,,,,
1,RIM-001,Alloy Rim,2,item,RimSupply,RS-RIM-42,Aluminum Alloy 6061,0.3,kg
```
If a re-import isn't adding inputs to a product, the product is most likely already LIVE with a model. See [Import troubleshooting](/docs/import/import-troubleshooting#input-deduplication) for how to add to it.
## Suppliers
Creates a **supplier company** for each row, plus **contacts** when contact columns (name or email) are present.
* **Matched by:** UUID, then your internal supplier ID, then supplier name.
* **On a match:** the existing supplier is reused and left untouched. A contact is added only when no contact with the same email or name is already on that supplier.
* **Created new when:** no supplier matches by ID or name.
**Sample rows:**
```csv theme={"system"}
name,orgNumber,internalId,contactName,contactEmail
RimSupply,123456789,SUP-001,John Doe,john@rimsupply.com
TireCo,987654321,SUP-002,Jane Smith,jane@tireco.com
```
## Locations
Creates a **named location** for each row, optionally geocoded to coordinates when address details are provided.
* **Matched by:** location name, within the owning company - the company named in the `companyId` column, or your own company for locations you hold directly.
* **On a match:** the existing location is reused. Address fields from your file **fill in blanks** but do **not** overwrite values that are already set.
* **Created new when:** no location with that name exists for that company.
**Sample rows:**
```csv theme={"system"}
name,address1,postCode,city,country,type
Bike Assembly Plant,Industriveien 12,3045,Drammen,Norway,factory
Oslo Parts Warehouse,Torggata 23,0183,Oslo,Norway,warehouse
```
## Activity data, air travel, and transport
These three types record **transactions and events** - a purchase, a sale, a flight, a shipment. Each row becomes one activity record. Air travel and transport are activity types with their own columns (flight details, logistics weight and distance, and so on).
* **Matched by:** nothing. Imported activity rows are not matched against existing activities.
* **Always created new:** every row produces a new record on every import.
**Sample rows - activity data:**
```csv theme={"system"}
name,direction,startDate,endDate,quantity,unitCode,productName,productSku
Aluminum tubing purchase Q1,Purchase,2026-01-15,2026-01-15,1200,kg,Aluminum Alloy 6061,ALU-6061
```
**Sample rows - air travel:**
```csv theme={"system"}
direction,name,startDate,flightRoute,flightCabinClass,quantity
Purchase,Supplier visit Oslo to Taipei,2026-02-03,OSL-TPE,Economy,8300
```
**Sample rows - transport:**
```csv theme={"system"}
direction,transportTypeName,startDate,endDate,name,logisticsWeightInKg,logisticsDistanceInKm
Purchase,Sea,2026-03-01,2026-03-20,Frame shipment Shanghai to Oslo,5000,18000
```
Because activity rows aren't deduplicated, **re-importing the same activity file will create the records again.** Import each period's data once. To correct activities you've already imported, edit or delete them in the app or via the API rather than re-importing - see the [Activity API](/api-reference/v1/activity/patch).
## Import match mode (products and materials)
Product and material imports let you choose what happens to **matched** rows. A match-mode selector appears on the upload screen for these two import types. **The default depends on the type** - product imports start on **Create new only**, material imports on **Fill missing only** - but you can switch to any mode before you submit:
* **Create new only** (default for product imports) - matched rows are skipped, and only new items are added. This is the safe, no-surprises option, and the same way supplier and BOM imports already behave.
* **Overwrite existing** - the values in your file win. Each scalar field on the matched item is replaced with the imported value wherever your file provides one.
* **Fill missing only** (default for material imports) - only fields that are currently **empty** are filled in; values you've already set are kept. A stored `0` or an empty text value counts as set and is preserved - Variable treats *zero as a value and only "missing" as missing*.
Use overwrite or fill-missing to backfill enriched source data - weights, CPC/HS codes, descriptions, notes - onto items you created earlier, without re-keying them by hand.
**Scalar fields only.** Overwrite and fill-missing update an item's own fields - weight, SKU, codes, description, notes, and the like. They never touch its **relationships**: the supplier, assigned datasets, and bill-of-materials inputs on an existing item are left exactly as they are, in every mode. Updating those by import is a planned follow-up.
Because matched rows are still deduplicated by SKU (or name), re-importing never creates duplicates regardless of mode - the only question is whether matched items are left alone, overwritten, or topped up.
### Undoing an update
Deleting an import doesn't just remove the items it created - it also **reverts the scalar updates** it made to pre-existing items. Overwritten fields are restored to their prior values, and fields it filled in are cleared again. The revert is guarded: any field you (or a later import) changed after the import is left exactly as it is, so a delete never clobbers newer edits. When some fields are skipped for that reason, the app tells you how many couldn't be reverted.
### Other types
Suppliers and BOMs always **reuse, not update** a matched record, so a re-import won't rewrite curated data. Locations are a partial exception - a re-import fills in address fields that were previously blank but won't overwrite values that are already set. Activity, air travel, and transport rows are never matched and always create a new record. To change one of these after import, edit it directly in the app or via the API.
## Next steps
* [Data import overview](/docs/import/import-overview) - the import workflow end to end
* [What happens after upload](/docs/import/import-materials) - the 3-level BOM structure
* [Import troubleshooting](/docs/import/import-troubleshooting) - duplicate handling and common errors
# What happens after upload
Source: https://docs.variable.global/docs/import/import-materials
How Variable processes your BOM data and creates products, materials, and relationships
When you submit a BOM import, Variable performs several operations to create your product structure. This guide explains what happens behind the scenes.
## Import processing overview
```mermaid theme={"system"}
flowchart TD
A[BOM File Submitted] --> B[Create Root Products]
B --> C[Create Supplier Parts]
C --> D[Create Materials]
D --> E[Link Inputs to Products]
E --> F[Assign Suppliers]
F --> G[Calculate Footprints]
style A fill:#d1edff,stroke:#d1edff,color:#003540
style B fill:#ede5ff,stroke:#ede5ff,color:#003540
style C fill:#ede5ff,stroke:#ede5ff,color:#003540
style D fill:#ede5ff,stroke:#ede5ff,color:#003540
style E fill:#e8f2a1,stroke:#e8f2a1,color:#003540
style F fill:#e8f2a1,stroke:#e8f2a1,color:#003540
style G fill:#f7edcf,stroke:#f7edcf,color:#003540
```
## What gets created
### Level 0: Root products
Rows with `bomLevel: 0` or no parent reference become root **Products**. These are your company's own products that you manufacture or assemble.
```csv theme={"system"}
bomLevel,productSku,productName
0,BIKE-001,Mountain Bike
```
Creates a Product:
* Type: LIVE (or UNKNOWN if incomplete)
* Owned by your company
* Ready to receive inputs from child rows
If a product with the same SKU already exists, Variable reuses it instead of creating a duplicate.
### Level 1+: Supplier parts and materials
Child rows create a chain of related items. Variable uses a **3-level structure** to accurately model your supply chain:
```mermaid theme={"system"}
flowchart TB
A[Your Product] --> B[Supplier Part]
B --> C[Material]
C --> D[Dataset]
style A fill:#d1edff,stroke:#d1edff,color:#003540
style B fill:#ede5ff,stroke:#ede5ff,color:#003540
style C fill:#e8f2a1,stroke:#e8f2a1,color:#003540
style D fill:#f7edcf,stroke:#f7edcf,color:#003540
```
| Level | What it represents | Example |
| :- | :- | :- |
| Product | Your finished good or assembly | Mountain Bike |
| Supplier Part | Specific part from a specific supplier | "Alloy Rim from RimSupply" |
| Material | Generic material type | Aluminum Alloy 6061 |
| Dataset | Emission factors from database | ecoinvent: Aluminum production |
## The 3-level BOM structure explained
### Why three levels?
Consider this scenario: you buy an "Alloy Rim" from "RimSupply". That part is made of aluminum. To calculate its carbon footprint, you need to:
1. Know the **part** (Alloy Rim) and **quantity** you're using
2. Know the **supplier** (RimSupply) to track your supply chain
3. Know the **material** (aluminum) to assign the correct impact data
Variable maintains all three pieces of information.
### How supplier parts work
When you import a BOM row with supplier information:
```csv theme={"system"}
productSku,supplierName,supplierPartId,materialName,quantity,unitCode
RIM-001,RimSupply,RS-RIM-42,Aluminum Alloy,0.3,kg
```
Variable creates:
1. **Supplier Part** - A Material node representing "RS-RIM-42 from RimSupply"
* Linked to the supplier company
* Stores the supplier's part number
2. **Material** - A Material node representing "Aluminum Alloy"
* Generic material type
* Where you'll assign the dataset
3. **Input relationships**
* Your Product uses the Supplier Part (quantity from BOM)
* The Supplier Part contains the Material (weight from BOM)
## Weight assignment rules
How Variable assigns weights depends on the chain depth:
### 3-level chain (Part to Supplier Part to Material)
When the supplier part has an associated material:
* **Supplier Part** gets quantity from BOM (e.g., 1 piece)
* **Material** gets weight from BOM (e.g., 0.3 kg)
```csv theme={"system"}
productSku,supplierPartId,materialName,quantity,weightAmount,weightUnitCode
PEDAL-001,SUP-PEDAL-1,Aluminum Alloy,2,0.15,kg
```
Result: The product uses 2 pedals, each pedal contains 0.15 kg of aluminum.
### 2-level chain (Part to Supplier Part only)
When there's no separate material:
* **Supplier Part** gets both the quantity and the weight unit
* The supplier part IS the material input
```csv theme={"system"}
productSku,supplierPartId,quantity,weightAmount,weightUnitCode
FRAME-COAT-001,SUP-COAT-1,1,0.8,kg
```
Result: The product uses 0.8 kg of frame coating (the supplier part itself is the material input).
Variable detects the chain depth automatically by checking if any row for a given supplier part also has a `materialId` or `materialName`.
## Supplier relationships
When you include supplier columns:
| Column | What happens |
| :- | :- |
| `supplierName` | Creates or matches a Company node |
| `supplierId` | Uses your internal ID for matching |
| `supplierPartId` | Stored on the supplier part for reference |
Supplier matching:
* First tries to match by `supplierId`
* Then by `supplierName`
* Creates a new supplier if no match found
## Idempotency: safe re-imports
BOM imports are designed to be re-run safely:
### Product deduplication
* Products matched by SKU within your company
* Existing products are **reused, not recreated** - and not updated either. Variable leaves existing matches alone; it won't overwrite their name, supplier, or other fields with newer values from your file.
### Input deduplication
New inputs are only added when:
* The product was created during this import, OR
* The product has type UNKNOWN, OR
* The product has no existing inputs
Products that are already LIVE with inputs are considered "fully modeled" and won't receive additional inputs from a re-import.
If you need to add inputs to an existing LIVE product, you can either change it to UNKNOWN first, or add inputs manually through the UI.
### Duplicate detection
Within the same import, duplicate inputs are detected by comparing:
* Source product ID
* Quantity
* Unit code
## After import completes
### Automatic calculations
Variable automatically triggers CO2e recalculation for all affected products. This cascades through your product hierarchy.
### What you need to do next
Materials are created but not yet linked to datasets. Assign a dataset to each one in the app to complete your footprint calculations.
## Related relationships created
After a BOM import, these relationships exist in Variable:
```mermaid theme={"system"}
%%{init: {'theme':'base', 'themeVariables': {'primaryColor':'#d1edff','primaryBorderColor':'#BDB8B2','primaryTextColor':'#003540','lineColor':'#BDB8B2'}}}%%
erDiagram
DataImport ||--o{ Product : "IMPORTED_WITH"
DataImport ||--o{ Material : "IMPORTED_WITH"
DataImport ||--o{ Input : "IMPORTED_WITH"
Product ||--o{ Input : "HAS_INPUT"
Input ||--|| Material : "USING_SOURCE"
Material ||--o| Company : "SUPPLIED_BY"
Material ||--o| Dataset : "REFERENCES (after assignment)"
```
## Next steps
* [Troubleshooting](/docs/import/import-troubleshooting) - Resolve common import issues
# Data import overview
Source: https://docs.variable.global/docs/import/import-overview
Bulk import products, materials, BOMs, suppliers, activities, and more into Variable
Variable's data import system lets you quickly populate your workspace with bulk data from spreadsheets, ERP exports, or other sources. The same wizard handles every import type - pick the type that matches your data, upload a file, map your columns, and submit.
## What you can import
| Import type | Description |
| :- | :- |
| **Products** | Your company's own products with SKUs and attributes |
| **Materials** | Material inputs and source products from suppliers - unprocessed, processed, or pre-formed |
| **Bill of materials (BOM)** | Bill of materials linking products to their inputs, with optional supplier and material breakdowns |
| **Suppliers** | Supplier companies and contacts |
| **Locations** | Named locations (factories, warehouses, ports) with optional address geocoding |
| **Activity data** | Purchase and sales transactions for GHG inventory tracking |
| **Air travel** | Air travel emissions data (flights, distances, classes) |
| **Transport** | Logistics and freight emissions data |
## Supported file formats
Variable accepts the following file formats:
| Format | Extension |
| :- | :- |
| Comma-separated values | `.csv` |
| Excel spreadsheet | `.xlsx` |
CSV and XLSX work equally well. For multi-sheet XLSX workbooks, you'll be prompted to pick which sheet to import. See [Troubleshooting](/docs/import/import-troubleshooting) for tips on merged cells, hidden rows, and formulas.
## Import workflow
The import process follows these steps:
```mermaid theme={"system"}
flowchart TB
A[Upload File] --> B[Parse & Detect]
B --> C[Map Columns]
C --> D[Map Values]
D --> E[Submit Import]
style A fill:#d1edff,stroke:#d1edff,color:#003540
style B fill:#d1edff,stroke:#d1edff,color:#003540
style C fill:#ede5ff,stroke:#ede5ff,color:#003540
style D fill:#ede5ff,stroke:#ede5ff,color:#003540
style E fill:#e8f2a1,stroke:#e8f2a1,color:#003540
```
Choose the import type, set the data source system and decimal separator, and upload your CSV or XLSX file. For multi-sheet XLSX workbooks, pick the sheet to import.
Variable parses your file and identifies columns. AI-assisted mapping suggests how your columns map to Variable fields.
Review and adjust column mappings. The system learns from your previous imports.
Configure how individual values are transformed: units, dates, and enum values.
Review your data and submit. Variable creates the records and calculates footprints.
## Key benefits
### Bulk creation
Import thousands of products, materials, or activities in a single operation instead of creating them one by one.
### AI-assisted mapping
Variable uses AI to suggest how your column headers map to Variable fields, reducing manual configuration.
### Learning from history
The system remembers your previous import mappings. When you import similar files, it automatically applies your past configurations.
### Idempotent re-imports
Re-importing the same file produces no duplicate data - products are matched by SKU (or by name when no SKU is given) and inputs are deduplicated automatically.
This applies to products, materials, suppliers, locations, and BOMs. **Activity, air travel, and transport imports are not deduplicated** - each row becomes a new record every time. See [Import behavior by type](/docs/import/import-behavior) for the per-type rules.
For **products and materials**, you choose what a re-import does to matched items with an [import match mode](/docs/import/import-behavior#import-match-mode-products-and-materials) on the upload screen - **Create new only** (the product default, leaves them untouched), **Overwrite existing**, or **Fill missing only** (the material default, fills blank fields). Only scalar fields are updated; relationships (supplier, datasets, inputs) are never changed. Other import types reuse matched records without overwriting.
### Automatic calculations
After import, Variable automatically calculates CO2e footprints wherever it can - which means wherever a dataset is assigned to supply the emission factors. Products and materials that don't yet have a dataset assigned have nothing to calculate; once you assign one, Variable recalculates automatically.
## Next steps
* [Import behavior by type](/docs/import/import-behavior) - What each type creates and when re-imports create new vs. reuse
* [Prepare your BOM file](/docs/import/prepare-bom) - Learn about required and recommended columns
* [Upload and map columns](/docs/import/upload-map-columns) - Step-by-step import process
* [What happens after upload](/docs/import/import-materials) - Understand how Variable processes your data
* [Troubleshooting](/docs/import/import-troubleshooting) - Solve common import issues
# Import troubleshooting
Source: https://docs.variable.global/docs/import/import-troubleshooting
Solve common data import issues
This guide helps you diagnose and resolve common import problems.
## Common errors and solutions
### Missing required columns
**Error:** `Required column 'productSku' is not mapped`
**Cause:** Your file doesn't have a column mapped to a required field.
**Solution:**
1. Check that your file has a column containing product SKUs
2. The column might have a different name (e.g., "Part Number", "Item Code")
3. In the mapping step, manually map your column to `productSku`
Required columns vary by import type. For BOM imports, `productSku` and `quantity` are always required.
### Invalid unit codes
**Error:** `Unknown unit 'kilogrammes'`
**Cause:** Your unit values don't match Variable's standard units.
**Solution:**
1. In the value mapping step, map your unit to a standard Variable unit
2. Common mappings:
* `kilogrammes` to `kg`
* `pieces` or `units` to `pcs`
* `square meters` to `m2`
### Date format issues
**Error:** `Invalid date format in column 'startDate'`
**Cause:** Variable couldn't parse your date values, or the **Date format** dropdown doesn't match the format in your file.
**Solution:**
1. On the column-mapping screen, set the **Date format** dropdown to match your file (Day / Month / Year, Month / Day / Year, Year / Month / Day, Year Month, Month Year, or ISO 8601).
2. Make sure every row in the same date column uses the same format - mixed formats in one column will fail.
3. See [Date format](/docs/import/upload-map-columns#date-format) for the full list of accepted variations within each option.
Mixing date formats in the same column will cause parsing errors. Clean your data so every value in the column uses the same format before importing.
### Decimal separator problems
**Error:** Quantities appear wrong (e.g., `1234` instead of `1.234`)
**Cause:** Wrong decimal separator selected.
**Solution:**
1. Check your file's number format:
* US/UK: `1,234.56` (comma for thousands, period for decimals)
* European: `1.234,56` (period for thousands, comma for decimals)
2. Select the correct decimal separator in the import settings
### Empty or whitespace-only values
**Error:** `Required value missing in row 47`
**Cause:** A required field has empty or whitespace-only values.
**Solution:**
1. Check your source file for empty cells in required columns
2. Remove or fill in missing values before re-importing
3. Consider whether the row should be included at all
## Duplicate handling (idempotency)
Variable is designed to handle re-imports safely. Understanding how deduplication works helps avoid unexpected results.
### Product deduplication
Products are matched by SKU within your company:
* Same SKU = same product (reused, not duplicated)
* Different SKU = different product (new one created)
By default, re-imports reuse a matched product without overwriting it. For **material and product imports** you can change this with the [import match mode](/docs/import/import-behavior#import-match-mode-products-and-materials) on the upload screen - **Overwrite existing** or **Fill missing only** update scalar fields on matches (relationships stay untouched). BOM imports always reuse existing products. Otherwise, to land fixes from your source file, edit the items directly in the app or via the API.
### Input deduplication
Inputs are only added to products when:
* The product was **created during this import**, OR
* The product has type **UNKNOWN**, OR
* The product has **no existing inputs**
**Why is my product not getting new inputs?**
If a product is already LIVE with inputs, it's considered "fully modeled" and won't receive additional inputs from re-imports.
**Solution:**
1. Change the product to UNKNOWN before re-importing
2. Or add inputs manually through the UI
3. Or create inputs via the API
This behavior prevents accidental duplication when re-running imports. It's a safety feature, not a bug.
### Detecting duplicates within a file
Variable detects duplicate inputs by comparing:
* Source product/material ID
* Quantity
* Unit code
Duplicates within the same import are automatically deduplicated.
## Large file tips
### Performance optimization
For imports with 10,000+ rows:
1. **Clean your data first**
* Remove empty rows
* Remove summary/total rows
* Ensure consistent formatting
2. **Split by product**
* Import each top-level product separately
* Easier to track and troubleshoot
3. **Monitor progress**
* Large imports process in batches (250-1,000 items)
* You can navigate away - you'll be notified when complete
Import progress is tracked in the background. Check the import detail page for status updates.
## Import status reference
| Status | Description | Action |
| :- | :- | :- |
| created | Import record created | Upload a file |
| uploaded | File received and parsed | Configure mappings |
| processing | Import in progress | Wait for completion |
| done | Import completed | Review results |
| error | Import failed | Check error message, fix, and retry |
| stopped | Import cancelled | Re-start if needed |
### Recovering from errors
When an import fails:
1. **Check the error message** - Usually indicates the specific problem
2. **Review your data** - Look for the row or column mentioned
3. **Fix the source file** - Correct the issue in your original file
4. **Re-upload** - Create a new import with the corrected file
Failed imports may have partially created data. Check for any products or materials created before the failure.
## File format issues
### CSV parsing problems
**Symptoms:** Columns misaligned, data in wrong fields
**Common causes:**
* Commas in data values (not quoted)
* Inconsistent quoting
* Wrong delimiter (semicolon vs comma)
**Solutions:**
1. Ensure values with commas are quoted: `"Saddle, leather"`
2. Use consistent quoting throughout
3. Check your regional CSV settings
### XLSX parsing problems
**Symptoms:** Data missing or incorrect, or a "Could not read the spreadsheet" message on upload
**Common causes:**
* Wrong sheet selected (multi-sheet workbooks default to the first sheet)
* Merged cells
* Hidden rows or columns
* Formulas instead of values
* File is corrupt or password-protected, so it can't be opened at all
**Solutions:**
1. On upload, use the **Select sheet to import** dropdown to pick the correct sheet
2. Unmerge cells
3. Unhide rows and columns
4. Copy and paste as values to resolve formulas
5. If you see "Could not read the spreadsheet", open the file and re-save it as `.xlsx`, removing any password protection
## Getting help
If you're still experiencing issues:
1. **Check the error details** - Expand the error message for more information
2. **Export your mappings** - Useful for support debugging
3. **Contact support** - Include the import URL and error message
When contacting support, include:
* The import URL (from your browser's address bar)
* The error message
* A sample of the problematic data (anonymized if needed)
## Related guides
* [Preparing your BOM file](/docs/import/prepare-bom) - Data format requirements
* [Uploading and mapping columns](/docs/import/upload-map-columns) - Mapping configuration
* [What happens after upload](/docs/import/import-materials) - Understanding the import process
# Preparing your BOM file
Source: https://docs.variable.global/docs/import/prepare-bom
Required and recommended columns for BOM imports
A well-structured BOM (Bill of Materials) file makes imports faster and more accurate. This guide covers the columns Variable expects and best practices for formatting your data.
## Required columns
Every BOM import must include these columns:
| Column | Description | Example |
| :- | :- | :- |
| `productSku` | Unique identifier for the product or part | `BIKE-001` |
| `quantity` | Amount of this input used per unit of the parent product | `2.5` |
If your file is missing either `productSku` or `quantity`, the import will fail during validation.
## Recommended columns
These columns improve the quality of your imported data:
| Column | Description | Example |
| :- | :- | :- |
| `productName` | Human-readable name for the product | `Front Wheel Assembly` |
| `unitCode` | Unit of measure for the quantity | `kg`, `m2`, `pcs` |
| `bomLevel` | Hierarchy level (0 = root product) | `0`, `1`, `2` |
| `bomParentId` | SKU of the parent product (alternative to bomLevel) | `BIKE-001` |
| `weightAmount` | Weight of the item | `3.2` |
| `weightUnitCode` | Unit for weight | `kg`, `g`, `lbs` |
| `supplierName` | Name of the supplier | `RimSupply` |
| `supplierId` | Your internal supplier identifier | `SUP-001` |
| `supplierPartId` | Supplier's part number | `RS-RIM-42` |
| `materialId` | Identifier for the underlying material | `ALU-6061` |
| `materialName` | Name of the underlying material | `Aluminum Alloy 6061` |
## Example CSV templates
### Minimal template
The simplest valid BOM with only required columns:
```csv theme={"system"}
productSku,quantity
FRAME-001,1
WHEEL-ASSY-001,2
TIRE-001,2
BRAKE-PAD-001,4
```
### Standard template
A typical BOM with common columns:
```csv theme={"system"}
bomLevel,productSku,productName,quantity,unitCode,weightAmount,weightUnitCode,supplierName
0,BIKE-001,Mountain Bike,1,pcs,,,
1,FRAME-001,Aluminum Frame,1,pcs,2.3,kg,FrameCorp
1,WHEEL-ASSY-001,Front Wheel Assembly,1,pcs,0.8,kg,WheelWorks
1,WHEEL-ASSY-002,Rear Wheel Assembly,1,pcs,0.9,kg,WheelWorks
2,TIRE-001,Mountain Tire,2,pcs,0.4,kg,TireCo
2,RIM-001,Alloy Rim,2,pcs,0.3,kg,RimSupply
```
### Hierarchical template with materials
A detailed BOM with supplier parts and materials:
```csv theme={"system"}
bomLevel,productSku,productName,quantity,unitCode,supplierName,supplierPartId,materialId,materialName,weightAmount,weightUnitCode
0,CHAIR-001,Office Chair,1,pcs,,,,,,
1,SEAT-001,Seat Assembly,1,pcs,SeatCo,SC-SEAT-42,FOAM-01,Polyurethane Foam,1.2,kg
1,BASE-001,Chair Base,1,pcs,MetalWorks,MW-BASE-7,STEEL-01,Carbon Steel,3.5,kg
2,WHEEL-001,Caster Wheel,5,pcs,WheelCo,WC-CAST-1,NYLON-01,Nylon 6,0.08,kg
1,ARM-001,Armrest,2,pcs,PlasticPro,PP-ARM-3,ABS-01,ABS Plastic,0.25,kg
```
## Hierarchy options
Variable supports two ways to define BOM hierarchy:
### Option 1: bomLevel
Use integer levels where 0 is the root product:
```csv theme={"system"}
bomLevel,productSku,quantity
0,TABLE-001,1
1,TABLETOP-001,1
1,LEG-FRAME-001,1
2,LEG-001,4
```
The system processes rows in order. Level 2 items become children of the most recent level 1 item - here, the four legs attach to the leg frame:
```text theme={"system"}
TABLE-001
├─ TABLETOP-001 (qty 1)
└─ LEG-FRAME-001 (qty 1)
└─ LEG-001 (qty 4)
```
### Option 2: bomParentId
Explicitly reference the parent product's SKU:
```csv theme={"system"}
productSku,bomParentId,quantity
TABLE-001,,1
TABLETOP-001,TABLE-001,1
LEG-FRAME-001,TABLE-001,1
LEG-001,LEG-FRAME-001,4
```
This produces the same hierarchy as the `bomLevel` example above:
```text theme={"system"}
TABLE-001
├─ TABLETOP-001 (qty 1)
└─ LEG-FRAME-001 (qty 1)
└─ LEG-001 (qty 4)
```
Use `bomParentId` when your BOM rows are not in hierarchical order or when you need explicit parent-child relationships.
## Mixed units
You can enter an input quantity in any unit the source product supports, even when it differs from that product's declared unit. For example, if a board is declared in `m2` but your BOM lists it as `2.4 m`, import the row as-is — set `unitCode` to the unit you have. Variable converts the value to the declared unit for impact calculations and keeps your entered value in the API.
No extra columns are needed. Conversion uses the length, volume, area, or weight factors set on the source product.
If a row's unit doesn't match the product's declared dimension and the source product has no matching conversion factor, that row is skipped and will not appear in your imported model. Add the relevant conversion factor to the product, or use the declared unit, to import the row.
## Best practices
### Use consistent SKUs
SKUs are used to match and deduplicate products. Use the same SKU format throughout your file and across imports.
### Decimal separator
Variable auto-detects decimal separators. If you use European format (comma as decimal separator), you can select this during the import mapping step.
```csv theme={"system"}
# US format
quantity
3.5
# European format
quantity
"3,5"
```
### Date format
Variable accepts several date formats (Day / Month / Year, Month / Day / Year, Year / Month / Day, Year Month, Month Year, ISO 8601) - you'll select the one that matches your file on the column-mapping screen. Within a single date column, every row must use the same format. ISO 8601 (`YYYY-MM-DD`) is a good default if you control the export:
```csv theme={"system"}
startDate
2024-01-15
```
See [Date format](/docs/import/upload-map-columns#date-format) for the full list of accepted variations within each option.
### Standard unit codes
Use standard unit abbreviations that Variable recognizes. Codes are case-insensitive - `kg`, `KG`, and `Kg` all resolve to the same unit, and Variable stores the canonical form shown:
| Category | Units |
| :- | :- |
| Mass | `mg`, `g`, `kg`, `t`, `kt`, `mt`, `Gt`, `oz`, `lbs` |
| Length | `mm`, `cm`, `dm`, `m`, `km`, `in.`, `ft`, `yd`, `mi.`, `NM` |
| Area | `mm2`, `cm2`, `m2`, `km2`, `ha`, `in2`, `ft2`, `yd2`, `acre` |
| Volume | `cm3`, `ml`, `dl`, `l`, `hl`, `m3`, `c.`, `pt`, `qt`, `gal` |
| Energy | `J`, `Wh`, `kWh`, `MWh`, `MJ`, `GJ` |
| Power | `W`, `kW`, `MW` |
| Time | `s`, `min`, `h`, `d`, `wk`, `mo`, `y` |
| Discrete | `item`, `pcs`, `shts` |
| Freight | `tkm`, `tm` |
| Passenger | `pkm`, `pm` |
During import, you can map non-standard unit values (e.g., "kilograms", "kilogrammes", "lb") to Variable's standard codes.
### Clean your data before import
* Remove empty rows at the end of the file
* Ensure column headers are in the first row
* Remove any summary or total rows
* Check for special characters that might affect parsing
## Next steps
* [Upload and map columns](/docs/import/upload-map-columns) - Start the import process
* [What happens after upload](/docs/import/import-materials) - Understand the 3-level BOM structure
# Uploading and mapping columns
Source: https://docs.variable.global/docs/import/upload-map-columns
Step-by-step guide to uploading files and configuring column mappings
This guide walks you through the import process from file upload to column mapping.
## Starting an import
Click the **Import** button on the page that matches your data - Products, Materials, Suppliers, Activities, Locations, or a Product's BOM. Then select the import type:
* **Products** - your company's products
* **Materials** - supplier materials and material inventory
* **Bill of materials (BOM)** - inputs/line items for a specific product (offered when starting from a Product)
* **Suppliers** - supplier companies
* **Activity data** - purchase/sales transactions for GHG inventory
* **Air travel** - flight emissions records
* **Transport** - logistics and freight emissions
* **Locations** - named factories, warehouses, ports, and other facilities
On the upload screen, set the file-level options before continuing:
* **Data source system** - defaults to **Spreadsheet**. For BOM imports, you can pick a specific export format (e.g., SAP S/4HANA) and Variable will run a built-in adapter to transform the file before mapping.
* **Decimal separator** - defaults to **Period (.)** for US/UK formatting. Switch to **Comma (,)** if your file uses European number formatting (`1.234,56`).
* **Match mode** (Materials and Products only) - decides what happens to rows that match an item you already have: **Create new only**, **Overwrite existing**, or **Fill missing only**. Materials default to **Fill missing only**, products to **Create new only**. See [Import match mode](/docs/import/import-behavior#import-match-mode-products-and-materials).
* **Drag file here or click to upload** - select your CSV or XLSX file.
* **Select sheet to import** (XLSX only) - for multi-sheet workbooks, pick which sheet contains your data. Defaults to the first sheet.
Click **Next** to parse the file and continue to column mapping.
## Column mapping
After upload, Variable parses your file and displays the column mapping interface.
### How AI-assisted mapping works
Variable uses AI to suggest mappings between your column headers and Variable fields. The system considers:
1. **Exact matches** - Headers that exactly match Variable field names
2. **Semantic similarity** - Headers with similar meaning (e.g., "Part Number" maps to `productSku`)
3. **Previous imports** - Mappings you've used before for similar files
Each mapping shows its source: **AI** for AI-suggested, **System** for exact matches, or **User** for manually configured mappings.
### Adjusting mappings manually
To change a mapping:
1. Click on the Variable field dropdown for the column
2. Select the correct field from the list
3. To remove a mapping, select "Do not import"
Required fields are marked with an asterisk (\*). The import cannot proceed until all required fields are mapped.
### Mapping preview
The preview shows how your data will be imported:
| Your column | Variable field | Sample values |
| :- | :- | :- |
| Part Number | productSku | BIKE-001, RIM-001 |
| Description | productName | Mountain Bike, Alloy Rim |
| Qty | quantity | 5, 12.5 |
Review the sample values to ensure the mapping is correct.
## Value mapping
After column mapping, configure how values are transformed.
### Unit mapping
Map your unit values to Variable's standard units:
| Your value | Variable unit |
| :- | :- |
| kilograms | kg |
| pieces | pcs |
| square meters | m2 |
Variable recognizes many unit variations automatically. You should only need to map uncommon abbreviations.
### Date format
If your file has date columns, use the **Date format** dropdown at the top of the mapping screen to tell Variable how to parse them. Pick the option that matches your file - Variable accepts several variations within each option:
| Format | Accepted variations |
| :- | :- |
| Day / Month / Year | `17/05/2026`, `17.05.2026`, `17-05-2026` |
| Month / Day / Year | `05/17/2026`, `05-17-2026` |
| Year / Month / Day | `2026/05/17`, `2026-05-17`, `20260517` |
| Year Month | `2026/05`, `2026-05`, `202605` |
| Month Year | `05/2026`, `05-2026`, `052026` |
| ISO 8601 | `2026-05-17T14:30:00Z` |
All dates in a date column must use the same format. Mixing formats within a single column will cause parsing errors - clean those up in your source file before importing.
## Reviewing your data
Before submitting, review the transformed data preview:
1. Check that quantities look correct (right decimal places)
2. Verify dates are parsed correctly
3. Confirm units are mapped appropriately
4. Review any rows flagged with warnings
### Validation warnings
The system highlights potential issues:
* **Missing required values** - Rows without productSku or quantity
* **Invalid units** - Unit values that couldn't be mapped
* **Invalid dates** - Values that don't match the date format you selected. These show as `Invalid Date` rather than being read as a different day
## Submitting the import
Once mappings are configured:
1. Click **Submit Import**
2. The import processes in the background
3. You can navigate away - you'll be notified when complete
Large imports (10,000+ rows) may take several minutes. The system processes data in batches of 250-1,000 items.
### Import status
Track your import progress:
| Status | Description |
| :- | :- |
| created | Import record created, awaiting file |
| uploaded | File received, awaiting processing |
| processing | Import in progress |
| done | Import completed successfully |
| error | Import failed - check error message |
| stopped | Import was manually cancelled |
## Saving mapping configurations
Variable automatically saves your mappings. The next time you import a similar file:
* Previously used mappings are suggested first
* Column order doesn't need to match
* New columns are mapped using AI
This learning improves with each import you complete.
## Next steps
* [What happens after upload](/docs/import/import-materials) - Understand how Variable processes BOM data
* [Troubleshooting](/docs/import/import-troubleshooting) - Resolve common import issues
# Products & materials
Source: https://docs.variable.global/docs/material
A **Product** (LCA) is a modeled footprint with a bill of materials - its Inputs point to **Elements** (Materials, Energy, Transport, or Process), to sub-Products, or to Datasets directly. A **Material** is one kind of Element; it gets its impacts by referencing a **Dataset** directly rather than modeling its own BOM. **Datasets** are the original source of impact data. See [Data Model](/docs/data-model) for the full picture.
API documentation: [Product ](/api-reference/v1/product), [Material ](/api-reference/v1/material), [Database search ](/api-reference/v1/database)
### Attributes
| Attribute | Type | Description | Example |
| :- | :- | :- | :- |
| uuid | string | The unique identifier | 123e4567-e89b-12d3-a456-426614174000 |
| name | string | The name of the material | Steel |
| sku | string | The stock keeping unit | ST123 |
| unit | Unit | The unit of measure | kg |
| weight | Amount | The user-declared weight of the material | `{ quantity: 100, unit: "kg" }` |
| packagingWeight | Amount | The user-declared weight of the material's packaging | `{ quantity: 10, unit: "kg" }` |
| modeledWeight | Amount | Read-only. Server-computed sum of the model's A1–A3 product material input weights (Material group "Product"; excludes packaging and ancillary) | `{ quantity: 100, unit: "kg" }` |
| modeledPackagingWeight | Amount | Read-only. Server-computed sum of the model's A1–A3 packaging input weights (Material group "Packaging"); compare against the declared `packagingWeight` | `{ quantity: 12.5, unit: "kg" }` |
| modeledTotalWeight | Amount | Read-only. Gross modeled weight: `modeledWeight` + `modeledPackagingWeight`, derived at read time (a missing addend contributes nothing); present when at least one of the two is present | `{ quantity: 112.5, unit: "kg" }` |
| recycledPercent | number | The user-declared recycled content of the material as a percentage (0–100). A `0` is a declared zero; absent when undeclared | 30 |
| modeledRecycledPercent | number | Read-only. Server-computed weight-weighted average of the contributing inputs' recycled content (0–100); compare against the declared `recycledPercent` | 27.5 |
| startDate | date | The date the model is valid from | 2021-01-01 |
| endDate | date | The date the model is valid until | 2021-12-31 |
| dataQualityIndicators | object | Information about the quality of the material | `{ technologicalDQR: 2, ... }` |
| dataQualityRating | number | A number from 1 to 3 representing the quality of the material | 1 |
| taxonomy | Taxonomy | The category of the material | Steel |
| geoLocation | GeoLocation | The location of the material | Pittsburgh, PA |
| location | Location | The named location of the material | Pittsburgh Warehouse |
| dataSources | DataSource | The source of the data | `[{ name: "ecoinvent" }]` |
| created | date | The date the material was created | 2021-01-01T00:00:00.000Z |
| updated | date | The date the material was last updated | 2021-01-22T00:00:00.000Z |
#### Data Quality
Both of these follow the [Pathfinder framework](https://wbcsd.github.io/data-exchange-protocol/v2#dt-dataqualityindicators),
with some additional fields on the `dataQualityIndicators` object.
The `dataQualityIndicators` object contains information about the quality of the material.
```json theme={"system"}
{
"documentationYear": 2022,
"coveragePercent": 100,
"specificPercent": 0,
"averagePercent": 100,
"technologicalDQR": 2,
"temporalDQR": 2,
"geographicalDQR": 2,
"completenessDQR": 1,
"reliabilityDQR": 1
}
```
The `dataQualityRating` is a number from 1 to 3 that represents the quality of the material (1 being the best).
```json theme={"system"}
{
"dataQualityRating": 1
}
```
### Footprint
The `footprint.CO2e` object holds the CO2e values for the `Product` / `Material`.
```json theme={"system"}
{
"CO2e": {
"totalCarbonFootprint": "1234.5678",
"productCarbonFootprint": "1234.0",
"upstream": "1230.0",
"direct": "4.0",
"downstream": "0.0678",
"A1_A3": "1234.0",
"A1": "1200.0",
"A2": "30.0",
"A3": "4.0",
"A4": "0.5",
"B1": "0.06",
"B2": "0.007",
"B3": "0.0008",
"B4": "",
"B5": "",
"C1": "",
"C2": "",
"C3": "",
"C4": "",
"D": ""
}
}
```
### Diagram
```mermaid theme={"system"}
erDiagram
Material ||--o{ Model : "has"
Footprint ||--|| CO2e : ""
Material ||--|| Footprint : "has"
Material ||--|| Supplier : "product of"
Material ||--|| Taxonomy : "categorized in"
Material ||--|| "(Geo?)Location" : "located at"
Material ||--o{ DataSource : "has"
Material ||--o{ Documents : "has"
Material {
name string
sku string
unit Unit
weight Amount
dataQualityIndicators object
dataQualityRating number
}
CO2e {
productCarbonFootprint decimal
upstream decimal
direct decimal
downstream decimal
A1_A3 decimal
A1 decimal
A2 decimal
A3 decimal
A4 decimal
B1 decimal
B2 decimal
B3 decimal
B4 decimal
B5 decimal
C1 decimal
C2 decimal
C3 decimal
C4 decimal
D decimal
}
```
# Models & inputs
Source: https://docs.variable.global/docs/model
`Model`s have many `Input`s. The source of an `Input` can be:
1. a `Material` that is made in-house or sourced from a `Supplier`
2. a `Processing` activity
3. a `Transportation` activity
4. a `Use Stage` calculation
[Model API documentation ](/api-reference/v1/model)
### Attributes
| Attribute | Type | Description | Example |
| :- | :- | :- | :- |
| uuid | string | The unique identifier | 123e4567-e89b-12d3-a456-426614174000 |
| name | string | The name of the input | Steel Handle |
| quantity | number | The amount of the input | 100 |
| unit | Unit | The unit of measure | kg |
| order | number | The order of the input | 1 |
| material | Material | The material for the input | `{ uuid: "...", name: "Steel" }` |
| recycledPercent | number | Recycled content of this input as a percentage (0–100), inherited from its source product — the source's modeled recycled content when available, otherwise its declared value, or 0 when there is no source product or the source has neither | 30 |
| lifecycleStage | LCA\_Category | The stage of the life-cycle the input is in | `{ code: "A1", name: "Materials" }` |
| CO2e | decimal | The CO2e value of the input | 1234.5678 |
| created | date | The date the input was created | 2021-01-01T00:00:00.000Z |
| updated | date | The date the input was last updated | 2021-01-22T00:00:00.000Z |
`Input`s are categorized into Life-cycle assessment (LCA) categories.
### Diagram
```mermaid theme={"system"}
erDiagram
Product ||--o{ Model : "has"
Model ||--o{ Input : "has"
Input ||--o| Processing : "processed by"
Input ||--o| Transportation : "distributed by"
Input ||--o| Material : "using source"
Input ||--o| "Use Stage" : "has"
Input {
quantity number
unit Unit
CO2e decimal
lifecycleStage LCA_Category
}
```
# Security
Source: https://docs.variable.global/docs/security
Variable is committed to maintaining the security, confidentiality, and integrity of our customer's data. We have
implemented a comprehensive security program that includes administrative, technical, and physical safeguards to
protect the data that we process. You can see more details in our [Trust Center](https://trust.variable.global).
## How your data is protected
Variable's security program covers the people, processes, and infrastructure handling your data:
* **Encryption** - data is encrypted in transit (TLS) and at rest
* **SOC 2 Type II** - independently audited controls covering security, availability, and confidentiality
* **Regular testing** - third-party penetration tests and continuous internal security reviews
* **Role-based access** - owners, admins, contributors, and viewers, with per-organization scoping
* **SSO** - single sign-on with Microsoft Entra ID or Google Workspace, available for enterprise plans. See [Single sign-on](/docs/sso/overview)
For the full set of controls, certifications, and sub-processors, visit the [Trust Center](https://trust.variable.global).
## Reporting a vulnerability
If you believe you've found a security vulnerability in Variable, please report it to us privately at **[security@variable.co](mailto:security@variable.co)**. Please do not file a public issue.
Please include:
* A description of the issue and its potential impact
* Steps to reproduce, or a proof-of-concept
* The affected component, endpoint, or version (if known)
* Any relevant logs, screenshots, or sample payloads
A machine-readable version of this policy is published at [`/.well-known/security.txt`](https://app.variable.global/.well-known/security.txt) per [RFC 9116](https://www.rfc-editor.org/rfc/rfc9116).
## What to expect from us
When you report a vulnerability in good faith, we will:
* Acknowledge receipt within **3 business days**
* Make best efforts to keep you updated and promptly complete our investigation and, if applicable, confirm our remediation strategy within an established timeline
* Credit you in the release notes once a fix is shipped, if you'd like
## Scope
In scope:
* The Variable web application (app.variable.global)
* The Variable marketing site (variable.global)
* The Variable public API
* The Variable MCP endpoint
* Official Variable client libraries and integrations
Out of scope:
* Findings from automated scanners without a demonstrated impact
* Denial-of-service attacks, volumetric attacks, or rate-limit testing
* Social engineering of Variable employees, customers, or partners
* Physical attacks against Variable offices or infrastructure
* Issues in third-party services we depend on (please report those directly to the upstream vendor)
* Vulnerabilities requiring physical access to a user's device or a fully compromised account
## Safe harbor
We will not pursue legal action against researchers who:
* Make a good-faith effort to comply with this policy
* Avoid privacy violations, data destruction, and service disruption
* Give us reasonable time to remediate before any public disclosure
* Only interact with accounts they own or have explicit permission to test
## Coordinated disclosure
We prefer coordinated disclosure. Once a fix is deployed, we're happy to collaborate with you on a public write-up and will credit you appropriately.
# Set up SSO with Microsoft Entra ID
Source: https://docs.variable.global/docs/sso/azure-ad
Register Variable as an app in Microsoft Entra ID (Azure AD) and send us the credentials to switch on single sign-on
Microsoft renamed Azure Active Directory (Azure AD) to Microsoft Entra ID. The steps below use the current names; the older Azure AD portal labels map one to one.
You register Variable as an **app registration** in your tenant — think of it as issuing Variable a badge that lets it ask Microsoft "who is this person?". A tenant is your organization's own directory in Microsoft's cloud.
You need the **Application Administrator** or **Cloud Application Administrator** role in Entra ID, or **Global Administrator**. Read the [single sign-on overview](/docs/sso/overview) first if you have not verified your domain in Variable.
## Register the app
1. Sign in to the [Microsoft Entra admin center](https://entra.microsoft.com).
2. Go to **Identity** → **Applications** → **App registrations** and select **New registration**.
3. Set **Name** to `Variable`, or anything your team will recognize.
4. Under **Supported account types**, choose **Accounts in this organizational directory only (Single tenant)**. This pins sign-in to your directory, so accounts from other Microsoft tenants and personal Microsoft accounts are rejected.
5. Under **Redirect URI**, choose the **Web** platform and enter:
```text theme={"system"}
https://app.variable.global/api/auth/microsoft/callback
```
It must match exactly, including `https` and no trailing slash.
6. Select **Register**.
On the app's **Overview** page, copy two values:
* **Application (client) ID**
* **Directory (tenant) ID** — the ID itself, in the form `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`, not your domain name
## Check the permissions
Variable asks Microsoft for your users' name and email address, and nothing else.
1. Open **API permissions**.
2. Confirm **Microsoft Graph** → **User.Read** (Delegated) is listed. It is added by default.
3. Select **Grant admin consent for** your organization. Without it, each person is asked to approve access on their first sign-in, and if your tenant blocks user consent they cannot sign in at all.
Variable matches each person to their Variable user by email address, so invite people with the address they use to sign in to Microsoft.
## Create a client secret
1. Open **Certificates & secrets** → **Client secrets** → **New client secret**.
2. Give it a description such as `Variable SSO` and choose an expiry. Microsoft allows at most 24 months.
3. Select **Add**, then copy the secret **Value** straight away. Microsoft shows it once; the **Secret ID** column is not the secret.
Write the expiry date in your calendar. When the secret expires, SSO stops working for everyone until you send us a new one.
## Send us the credentials
Send us the following through an agreed-upon secure channel:
* Provider: Microsoft
* Application (client) ID
* Directory (tenant) ID
* Client secret **Value**
* The email domains to cover, each verified in Variable
* A contact who can test the sign-in
We confirm once it is switched on.
## Test it
1. Open a private browser window and go to `https://app.variable.global`.
2. Enter your work email and continue. You are sent to Microsoft.
3. Sign in. You land back in Variable.
## Troubleshooting
| Microsoft or Variable says | Cause | Fix |
| :- | :- | :- |
| `AADSTS50011: The redirect URI ... does not match` | The redirect URI in the app registration differs from the one above | Correct it under **Authentication** → **Web** |
| `AADSTS65001: The user or administrator has not consented` | Admin consent was not granted | Grant it under **API permissions** |
| `AADSTS50020` or `AADSTS700016` | The person is signing in from another tenant, or the client ID is wrong | Confirm the tenant ID and client ID you sent |
| Back on the sign-in page with no message | The client secret is wrong or expired, or the **Secret ID** was sent instead of the **Value**. Microsoft reports this to Variable, not to the person signing in | Ask us whether we see a secret error, then create a new secret and send the **Value** |
| Back on the sign-in page with no message | The tenant ID or client ID sent to us is wrong, the account signed in to Microsoft is not the address typed, or the sign-in did not start on Variable's sign-in page in the same browser | Recopy both IDs from **Overview**, and start from Variable's sign-in page with the account whose address you typed |
| **No access** screen | None of the account's addresses is on a domain verified in Variable and linked to the connection | Check the domain still shows **Verified** under **Settings** → **Company**, then contact us |
| A setup screen asking for a company name | Signed in, but not invited to Variable | See [invite people first](/docs/sso/overview#invite-people-first) |
## Restrict who can sign in (optional)
By default anyone in your tenant can start a sign-in. To limit it to specific people, open the app under **Enterprise applications**, then **Properties**, set **Assignment required?** to **Yes**, and add users or groups under **Users and groups**. People who are not assigned are stopped by Microsoft before they reach Variable.
# Set up SSO with Google Workspace
Source: https://docs.variable.global/docs/sso/google-workspace
Create an OAuth client in Google Cloud for your Google Workspace and send us the credentials to switch on single sign-on
Google Workspace sign-in runs through a Google Cloud project. You create an **OAuth client** there — think of it as issuing Variable a badge that lets it ask Google "who is this person?" — and the project's consent screen decides which Google accounts may use it.
You need a Google Workspace **super admin**, or an admin who can create projects in your organization's Google Cloud. Read the [single sign-on overview](/docs/sso/overview) first if you have not verified your domain in Variable.
## Create or choose a project
1. Open the [Google Cloud console](https://console.cloud.google.com) signed in with a Workspace admin account.
2. Use the project picker to select an existing project, or choose **New project** and name it `Variable SSO`. Make sure the project belongs to your Workspace organization, not to a personal account.
## Configure the consent screen
The consent screen is what people see when Google asks them to allow Variable to read their name and email.
1. Go to **Google Auth Platform** → **Branding** (older consoles: **APIs & Services** → **OAuth consent screen**) and complete the app name, support email and developer contact.
2. Under **Audience**, set the user type to **Internal**. This restricts sign-in to accounts in your Workspace organization. It is the control that keeps outside Google accounts out, so do not choose **External** unless you have discussed it with us.
3. Under **Data Access**, add the scopes `openid`, `.../auth/userinfo.email` and `.../auth/userinfo.profile`. These are the non-sensitive defaults and need no Google verification review.
With the audience set to **External**, any Google account can attempt to sign in and Google may show an "unverified app" warning. Internal apps skip both.
## Create the OAuth client
1. Go to **Google Auth Platform** → **Clients** (older consoles: **APIs & Services** → **Credentials** → **Create credentials** → **OAuth client ID**) and select **Create client**.
2. Set **Application type** to **Web application** and name it `Variable`.
3. Under **Authorized redirect URIs**, add:
```text theme={"system"}
https://app.variable.global/api/auth/google/callback
```
It must match exactly, including `https` and no trailing slash. Leave **Authorized JavaScript origins** empty.
4. Select **Create**, then copy the **Client ID** and **Client secret**. Keep the secret out of shared documents and chat.
## Send us the credentials
Send us the following through an agreed-upon secure channel:
* Provider: Google
* Client ID
* Client secret
* The email domains to cover, each verified in Variable, including your primary Workspace domain
* A contact who can test the sign-in
Google reports your organization's primary domain for every account, including people whose address is on a secondary domain. Variable only accepts an account whose primary domain is linked to the connection, so include it even if nobody signs in with it.
We confirm once it is switched on. Google client secrets do not expire on their own, but rotate yours on your usual schedule and send us the new one before you disable the old.
## Test it
1. Open a private browser window and go to `https://app.variable.global`.
2. Enter your work email and continue. You are sent to Google.
3. Choose your Workspace account. You land back in Variable.
Variable passes your domain to Google as a hint, so the account picker leans toward your Workspace accounts. The hint is a convenience, not a lock. The boundary is enforced twice: the **Internal** audience keeps outside accounts at Google, and Variable rejects an account whose email Google has not verified or whose Workspace domain is not linked to your connection.
## Troubleshooting
| Google or Variable says | Cause | Fix |
| :- | :- | :- |
| `Error 400: redirect_uri_mismatch` | The redirect URI on the OAuth client differs from the one above | Correct it under **Authorized redirect URIs**; changes can take a few minutes to apply |
| `Error 401: invalid_client` | Wrong client ID, or the client was deleted | Recopy the client ID from the **Clients** page and send it to us |
| `Error 403: org_internal` | The person signed in with an account outside your Workspace | Sign in with the Workspace account |
| `Access blocked: ... has not completed the Google verification process` | Audience is set to **External** | Switch the audience to **Internal** |
| Back on the sign-in page with no message | The Google account is not the address that was typed, the secret sent to us is wrong, your primary Workspace domain is not linked to the connection, or the sign-in did not start on Variable's sign-in page in the same browser | Start from Variable's sign-in page and sign in with the same address. If it happens again, contact us: we can check the secret and the linked domains |
| A setup screen asking for a company name | Signed in, but not invited to Variable | See [invite people first](/docs/sso/overview#invite-people-first) |
## Restrict who can sign in (optional)
To limit Variable to specific people, open the Admin console at [admin.google.com](https://admin.google.com), go to **Security** → **Access and data control** → **API controls** → **Manage third-party app access**, add the OAuth client by its client ID, and set access to **Limited** or **Specific Google Workspace groups**.
# Single sign-on
Source: https://docs.variable.global/docs/sso/overview
Let your team sign in to Variable with their Microsoft Entra ID (Azure AD) or Google Workspace account
Single sign-on (SSO) lets people sign in to Variable with the account they already use at work. Your identity provider (IdP) — the system that holds your company's user accounts — vouches for who they are, so nobody needs a separate Variable password, and disabling someone in your IdP stops them signing in through SSO.
Variable connects to your IdP over OpenID Connect (OIDC), an identity layer built on OAuth 2.0. We support two providers:
* **Microsoft Entra ID**, formerly Azure Active Directory (Azure AD)
* **Google Workspace**
SSO is available on enterprise plans.
These guides use the redirect address of the hosted app. If you run Variable yourself, register your own address instead and configure OAuth through the environment variables in [Self-hosted](/docs/self-hosted).
## How setup works
You create an app in your IdP and send us its credentials; we set up the connection on our side. SSO is not something you switch on in the app.
SSO is tied to the email domains you own, and only a verified domain can be linked. Follow [Verify your domain](#verify-your-domain) below.
Follow the guide for [Microsoft Entra ID](/docs/sso/azure-ad) or [Google Workspace](/docs/sso/google-workspace). It ends with a client ID and a client secret.
Through an agreed-upon secure channel, send us the provider, the client ID and client secret, the domains to cover (for Microsoft, also your tenant ID), and a contact who can test the sign-in.
SSO proves who someone is, but it does not add them to your account. See [Invite people first](#invite-people-first).
We confirm when the connection is live. Keep an owner signed in until a second person has signed in through SSO.
## Verify your domain
You need the admin role to verify a domain.
1. Go to **Settings** → **Company** and find the **Domains** card.
2. Select **Add Domain**, enter your domain, for example `acme.com`, and select **\[Verify Domain]** next to it.
3. Add the DNS TXT record shown: name it `_variable-domain-verify` on your domain, with the value Variable gives you. DNS changes can take up to 72 hours to propagate.
4. Select **Check now**. The domain shows **Verified** once the record is found.
Leave the TXT record in place. Variable re-checks it, and Microsoft sign-ins stop for a domain that is no longer verified.
Domains are matched exactly. `acme.com` does not cover `eu.acme.com`, so verify and link every domain your people sign in with, and list them all when you contact us. For Google Workspace, also include your primary Workspace domain, even if nobody signs in with it: Google reports it for every account in your organization.
## Invite people first
Signing in through your IdP does not by itself give anyone access to your Variable account, so invite people before they sign in:
1. Go to **Settings** → **Users** and select **Invite**.
2. Enter the same email address they use at work, and assign a role.
Someone who signs in without an invitation is not added to your account. Instead, Variable asks them to set up a new company account of their own. If that happens, sign them out, invite them, and have them sign in again.
The **Auto-join** switch on a verified domain does not apply to SSO sign-ins. Invite people instead.
## Keep it working
* **Client secret expiry.** Microsoft client secrets expire, at most 24 months after they are created. When one does, nobody at your company can sign in through SSO. Create a new secret before the old one lapses and send it to us through an agreed-upon secure channel.
* **Keep a way in.** Make sure at least one owner has confirmed they can sign in through SSO before you rely on it.
* **Offboarding.** Disabling a person in your IdP stops new SSO sign-ins, and a session they have open can stay active until it expires. To end someone's access immediately, also remove them from **Settings** → **Users** in Variable.
## Troubleshooting
| What you see | Likely cause | What to do |
| :- | :- | :- |
| The sign-in page asks for a password instead of sending you to your IdP | The email's domain is not linked to a connection | Confirm the exact domain shows **Verified** and was on the list you sent us, then contact us |
| A password is rejected with a message that the domain is managed by your organization's SSO provider | The connection for the domain is switched off. Password sign-in is blocked for the domain and the sign-in page does not send people to your IdP | Contact us to switch the connection on |
| A setup screen asking for a company name | The person was not invited to your account | Invite them from **Settings** → **Users**, then have them sign in again |
| Back on the sign-in page with no message | Variable or your IdP rejected the sign-in: a wrong or expired client secret, a tenant mismatch, a different account than the address typed, a sign-in that did not start on Variable's sign-in page in the same browser, a Google account whose Workspace domain is not linked to the connection, or more than one matching user account belonging to a company | Start again from Variable's sign-in page, typing the address of the account you sign in with. If it happens again, contact us with the time of the attempt: we see the reason in our logs |
| **No access** screen | Variable found more than one matching user account and none of them belongs to a company, or none of the Microsoft account's addresses is on a verified domain linked to the connection | Check the domain still shows **Verified**, then contact us with the address they sign in with |
Contact us through your agreed channel with the time of the attempt and the email address used.
# Switch
Source: https://docs.variable.global/docs/switch
Switches are used for comparing materials in the modeling interface.
[Switch API documentation ](/api-reference/v1/switch)
```mermaid theme={"system"}
erDiagram
Product ||--o{ Model : "has"
Model ||--o{ Input : "has"
Input ||--o| Material : "using source"
Material }o--o{ Switch : "source for"
Input ||--o| Switch : "using switch"
Material |o--o| Supplier : "product of"
```
# syncId
Source: https://docs.variable.global/docs/syncId
All objects have a `syncId` property that is used as an external identifier of the thing in Variable.
This is useful for identifying objects in Variable by the ID of the object in another system, such as ERP software.
The `syncId` is a string that can be set to any value, and it must be unique. If you have a Product or Material with a `syncId` of `123`,
you cannot create another Product or Material with the same `syncId`.
If you try to create a Product or Material with a `syncId` that already exists, you will get a `400 BadRequest` error.
# Configuration
Source: https://docs.variable.global/mcp/configuration
Connect popular MCP clients to Variable.
Before you start, confirm MCP is enabled for your account — check the **MCP URL** row in [Company settings](https://app.variable.global/company). Clients will connect even without it, but every tool call fails with an `MCP access is not enabled for this account…` error pointing at the EnableMcp flag.
The MCP endpoint is:
```
https://app.variable.global/mcp
```
## Chat clients
### Claude Desktop
Claude Desktop supports remote MCP servers with OAuth out of the box.
1. Open **Settings -> Connectors -> Add custom connector**.
2. Give it a name (e.g. `Variable`) and paste `https://app.variable.global/mcp` as the **URL**.
3. Claude Desktop opens a browser to Variable, you sign in, pick an account, and click **Allow**.
That's it. Claude Desktop refreshes the token automatically; you'll only see the consent screen again if you revoke access or sign out.
### ChatGPT
ChatGPT supports remote MCP servers as Connectors. Under **Settings -> Connectors -> Create**, paste `https://app.variable.global/mcp`, complete the OAuth flow in the popup, and the Variable tools become available in any conversation where Connectors are enabled.
### Microsoft Copilot Studio
Copilot Studio supports remote MCP servers as tools that can be added to an agent. In the agent's **Tools** section, add a new Model Context Protocol tool, paste `https://app.variable.global/mcp` as the server URL, and complete the OAuth sign-in for your Variable account. The Variable tools then become available to anyone using that agent.
See Microsoft's [Copilot Studio MCP docs](https://learn.microsoft.com/en-us/microsoft-copilot-studio/agent-extend-action-mcp) for the latest setup steps and admin requirements.
## Coding clients
### Claude Code
Claude Code (CLI and VS Code extension) supports remote MCP servers via `claude mcp add`:
```bash theme={"system"}
claude mcp add --transport http variable https://app.variable.global/mcp
```
Then run `/mcp` inside Claude Code and select **Authenticate** for the `variable` server. A browser opens for the OAuth consent step. Tokens persist across sessions.
To share the server with your team via version control, commit a `.mcp.json` file at your project root:
```json theme={"system"}
{
"mcpServers": {
"variable": {
"type": "http",
"url": "https://app.variable.global/mcp"
}
}
}
```
Everyone on the team gets the same config when they open the project in Claude Code.
### Cursor
Cursor supports remote MCP servers via `mcp.json`:
```json theme={"system"}
{
"mcpServers": {
"variable": {
"url": "https://app.variable.global/mcp"
}
}
}
```
Save the file (or use **Cursor Settings -> MCP -> Add new MCP server**) and Cursor will prompt for OAuth on first use.
### GitHub Copilot (VS Code)
GitHub Copilot in VS Code supports remote MCP servers via `.vscode/mcp.json` (workspace) or the global user configuration (**MCP: Open User Configuration**):
```json theme={"system"}
{
"servers": {
"variable": {
"type": "http",
"url": "https://app.variable.global/mcp"
}
}
}
```
VS Code prompts for OAuth on first use. See VS Code's [MCP servers docs](https://code.visualstudio.com/docs/copilot/customization/mcp-servers) for the latest details.
## Verifying the connection
Once you're connected, try asking your client *"list my Variable accounts"*. It will reply with the accounts you belong to, whether MCP is turned on for each one, and which one is currently active.
If MCP isn't turned on for the account you want, [email us](mailto:hello@variable.co?subject=MCP%20Access) and we'll enable it. To work in a different account right now, ask your client to switch — e.g. *"switch my Variable account to ACME Furniture"*. Your choice is remembered for that client, so future sessions start in the same place.
## Troubleshooting
| Symptom | Likely cause |
| - | - |
| `MCP access is not enabled for this account…` error on every tool call | The active account doesn't have the MCP flag enabled. Ask your client to list your Variable accounts to find one that does, or [email us](mailto:hello@variable.co?subject=MCP%20Access). |
| `401 Unauthorized` immediately after connecting | OAuth token expired or was revoked. Reconnect from your client's MCP settings to re-run the consent flow. |
| `403` with `code -32000` | MCP flag is off for the active account (same as the first row). |
| Stuck on the consent screen | Make sure you're signed in to [app.variable.global](https://app.variable.global) in the same browser. Consent is bound to your Variable session. |
| Client connects but no tools appear | Most clients require an explicit "Authenticate" step before they load the tool list. Trigger it from the client's MCP UI. |
# How it works
Source: https://docs.variable.global/mcp/how-it-works
How agents, MCP, and Variable fit together — and where your data flows.
When you connect an MCP client to Variable, three things are working together. An **Agent** does the thinking, **Variable** holds your data and decides what's allowed, and **MCP** is the wiring that connects them. The agent runs outside Variable — you might self-host it or use a third-party service like Claude or ChatGPT — so it sits on its own side of a trust boundary. Knowing which layer does what, and who runs it, helps you understand what data is visible to whom, and how to keep your data safe.
## The three roles
The LLM-powered client you choose to use — Claude, ChatGPT, Copilot, or one you run yourself. You pick it; Variable neither provides nor controls it. It reads your chat messages, decides what to do, and calls tools to get data or make changes.
Any data you send in a chat message or data that is read from Variable will be visible to the agent and to whoever hosts it.
The [open protocol](https://modelcontextprotocol.io) that lets agents and Variable communicate. It tells the agent how to send and receive data from Variable.
The source of truth. It stores your data, enforces permissions, and returns results to the agent. Variable never sees your chat messages.
## Where your data flows
Chat messages go to the agent, which decides what to do (respond directly, get or send data via an MCP, etc.). When the MCP receives a request, it passes it to Variable, which authenticates the request and enforces permissions. The results are sent back to the agent, which then responds to you.
Solid arrows are the request on its way in; dashed arrows are the data coming back.
## What stays where
The split of roles is also a set of trust boundaries. A few things follow from it:
* **The agent is separate from Variable — and where it runs is your trust decision.** The agent sits entirely outside Variable, whether you self-host it or use a third-party service (Anthropic, OpenAI, Microsoft, etc.). Whoever hosts it sees your prompts and every result the agent reads, under *their* terms — not Variable's. Self-hosting keeps that data inside your own network; with a third-party service, vet its data handling the way you'd vet any vendor before you connect it.
* **The agent reads everything in the conversation.** It has no direct line to the database, but every tool result it fetches lands in the chat and stays there — so the conversation holds whatever data you've pulled into it.
* **OAuth scopes every call to one account.** The agent can only touch your **active** Variable account. Switching accounts changes what it can reach — see [Switching accounts](/mcp/welcome#switching-accounts).
* **Variable enforces permissions, not the agent.** Each tool call runs under your account with the same rules as the app. The agent can't do anything you can't do yourself, and it can't see any data you don't have access to.
# MCP
Source: https://docs.variable.global/mcp/welcome
Connect Claude Desktop, Claude Code, Cursor, and other MCP clients to your Variable account.
**MCP is a preview feature.** It's still evolving — tools, behavior, and the set of available entities may change without notice, and it's enabled per account (see below). We'd love your feedback: [email us](mailto:hello@variable.co?subject=MCP%20Feedback).
**MCP access is enabled per account.** Check the **MCP URL** row in [Company settings](https://app.variable.global/company) to see whether it's on for your account. If it isn't, [email us](mailto:hello@variable.co?subject=MCP%20Access). Until then, MCP clients will connect but tool calls fail with an `MCP access is not enabled for this account...` error pointing at the EnableMcp flag.
## Endpoint
The MCP server runs at:
```
https://app.variable.global/mcp
```
## What you can do
You can manage your LCAs, inventory (materials, datasets, suppliers, and switches), activities, and transport.
You can also ask for visualizations and analysis — chart your emissions by lifecycle stage, compare products, spot hotspots, or summarize trends across your data.
## Switching accounts
To see all accounts you have access to (and which have MCP enabled):
```text Prompt theme={"system"}
list my Variable accounts
```
To swap the active one:
```text Prompt theme={"system"}
switch my Variable account to
```
# PACT Authentication
Source: https://docs.variable.global/pact-api/authentication
POST /auth/token
Get an access token to authenticate your requests.
The path is relative to your company specific url:
https\://**your\_subdomain**.variable.global/api
Unless you have a public API, you will need to authenticate your requests to the PACT API.
This is done using the OAuth 2.0 protocol.
# Configuration
Source: https://docs.variable.global/pact-api/configuration
Enable your PACT API in Variable in the "Variable Subdomain" section in [company settings](https://app.variable.global/company).
If you don't see the Variable Subdomain section, [email us](mailto:hello@variable.co), and we'll enable it for your account.
### Requirements
There are two parts to setting up your PACT API:
1. Getting your PACT API URL
2. Sharing your Product Carbon Footprints (PCFs) with the API
### Your PACT API URL
Your PACT API URL is based on your Variable subdomain. For example, if your subdomain is `furniture-co`,
your PACT API URL is:
[https://furniture-co.variable.global/api/pact/2/footprints](https://furniture-co.variable.global/api/pact/2/footprints)
### API Users
By default, users must authenticate to access your PACT API. You can add API users in the same section in
your [company settings](https://app.variable.global/company). Be sure to give them the `pact:read` permission.
### Visibility
You can also make your PACT API public to the internet by setting "Visibility" to "Public".
### Sharing Footprints
Locked Product Carbon Footprint (PCF) declarations that have a sharing setting of "API" or wider, and are conformant with
the [PACT standard](https://wbcsd.github.io/data-exchange-protocol/v2), are shown in the PACT API. A footprint's `id` is
its PCF's id. A product's own sharing setting doesn't decide what the API shows.
#### PACT requirements
Details on the PACT standard and required fields can
be found [in the PACT documentation](https://wbcsd.github.io/data-exchange-protocol/v2).
# PACT Footprint
Source: https://docs.variable.global/pact-api/footprint
GET /pact/2/footprint/{id}
The path is relative to your company specific url:
https\://**your\_subdomain**.variable.global/api
**Only footprints that are valid according to the PACT standard will show in the API, regardless of the sharing
setting.**
This generally means footprints that have all of the required fields filled out.
Details on the PACT standard and required fields can be found [in the PACT
documentation](https://wbcsd.github.io/data-exchange-protocol/v2).
# PACT Footprints
Source: https://docs.variable.global/pact-api/footprints
GET /pact/2/footprints
The path is relative to your company specific url:
https\://**your\_subdomain**.variable.global/api
**Only footprints that are valid according to the PACT standard will show in the API, regardless of the sharing
setting.**
This generally means footprints that have all of the required fields filled out.
Details on the PACT standard and required fields can be found [in the PACT
documentation](https://wbcsd.github.io/data-exchange-protocol/v2).
**Follow the `Link` header, not the page size.** A page can hold fewer footprints than the `limit`, or none, when
footprints that are not valid according to the PACT standard are left out. Request `next` until there is none.
# PACT API
Source: https://docs.variable.global/pact-api/welcome
Variable provides a PACT conformant API for sharing your Product Carbon Footprint (PCF) data with your customers.
## PACT: Partnership for Carbon Transparency
PACT is an open standard for sharing Product Carbon Footprint data. You can read more about it here: [https://www.carbon-transparency.org](https://www.carbon-transparency.org)
### Rate limit
To ensure the best performance for all users, we have a rate limit of **10 requests per second**.