# Brand settings
Source: https://docs.mangrovesystems.com/account-admin/brand-settings
The Brand Settings you configure in the Admin section allow you to apply your brand to external-facing parts of the software.
Here are the elements that can be configured for brand:
* **Verification Reporting**
* Web verification views that are embedded in the generated verification package for VVBs can have your brand applied. This includes setting your logo and colors on the verification views.
# Custom Fields
Source: https://docs.mangrovesystems.com/account-admin/custom-fields
Custom Fields extend the Mangrove data model to capture information that doesn't fit into standard fields, on projects and on assets. This enables each record to maintain a single source of truth for all relevant data, directly within Mangrove.
Instead of managing pathway- or customer-specific details in external resources, teams can define custom fields to store information such as verifiers, serial numbers, registration statuses, or other operational and MRV-related attributes. This reduces fragmentation, prevents data from falling out of sync, and minimizes reporting errors and validation delays.
Custom Fields are flexible and pathway-agnostic, ensuring Mangrove can support multiple project types without complicating the default user experience. Fields are only added where needed and can be accessed by the appropriate users, stakeholders, and systems.
Custom Fields are available on two record types:
* **Projects**: project-level attributes such as verifier, counterparty, or crediting period details.
* **Assets**: per-equipment attributes such as serial number, registration status, registry-assigned ID, or nameplate capacity. See [Manage assets](/production-accounting/assets).
### How it works
Custom Fields are managed in two distinct areas of the Mangrove platform: definitions live in Account Admin; values are set on the individual record.
#### Account Admin: Defining Custom Fields
Before a custom field can be used, it must first be defined at the account level. This is done in **Account Admin → Custom Fields**, which has a tab for each record type (Projects, Assets). Here, administrators create a library of custom fields available across the account, ensuring consistency and standardization of data fields across your organization.
Each definition specifies a **Label** (the field's display name) and a **Type**:
* **Text**, **Number**, **Boolean**, **Date**, **URL**: typed values validated on entry.
* **Select (enum)**: a closed list of allowed values, useful for statuses and other controlled vocabularies.
#### Setting values
* **Projects**: add a defined field to a project and set its value in the project's **Settings → Custom Fields** section, selecting only the fields relevant to that project.
* **Assets**: asset custom field values are set on the asset itself, in the project's **Settings → Assets** page, either when creating the asset or by editing it later.
### Permissions and Visibility
* Admins: Can define, add, remove, and edit custom fields and their values.
* Members: Can view the values of custom fields which are set as visible in the project overview.
Admins can also control the visibility of project custom fields, choosing whether they appear on the project's main Overview page. This allows for information to be tracked without being exposed to all users with read-only access to a project.
### API Access
For teams that need to integrate Mangrove with other systems, custom field data is accessible via the Mangrove API:
* **Definitions**: full CRUD via the [Custom Field Definitions](/api-reference/custom-field-definitions/get-all) endpoints.
* **Project values**: included as key-value pairs on the [project endpoints](/api-reference/projects/get).
* **Asset values**: readable and writable as `custom_field_values` on the [asset endpoints](/api-reference/assets/get-all), and filterable with `filter[custom_field_values][{key}]` when listing assets.
# Notifications
Source: https://docs.mangrovesystems.com/account-admin/notifications
In the dynamic environment of Mangrove, staying informed about ongoing activities and updates is crucial for effective management of production data and credit inventory. Stay updated on platform activities relevant to your projects and commercial operations through configured notifications.
Mangrove provides a notification system that helps users stay on top of changes and updates across the platform. Notifications can be received both **in-app** and **via email**, ensuring that users have immediate access to important information, regardless of their current activity on the platform.
* **In-App Notifications**: Users can find notifications in the "Notifications" section within Mangrove. This section provides a centralized feed of all recent notifications.
* **Email Notifications**: For users who prefer or require external alerts, Mangrove also offers the option to receive notifications by email. This ensures that important updates are not missed, even when users are away from the platform.
Notifications in Mangrove can be triggered by changes to specific resources within the platform. These resources include: **Projects, Production Data Events, Data Requests, Orders, Deliveries, Issuances.**
#### Configuring Notifications
Users with Admin privileges have the ability to configure notifications, tailoring the notification system to meet the specific needs of their team or organization. The configuration process involves selecting the specific object, the triggering condition, and optionally adding a time delay for the notification.
**To configure a new notification:**
1. Create a new notification in the `Admin` → `Notifications` settings
2. Choose which platform object (e.g., Projects, Orders) will trigger the notification.
3. **Define the Triggering Condition**: Specify what action or change to the object will initiate the notification. This could be an update to a project, a new order being placed, etc.
4. **Set a Time Delay (optional)**: Admins can add a delay to the notification, specifying that it should only be sent after a certain number of days post the triggering event.
5. **Notification Delivery Options**: Determine how the notification will be delivered. All notifications will appear in-app, but admins can also choose to send notifications via email to specific addresses.
Dynamic variables can be added to the notification message, as well as the email subject. A custom message can be written for email notifications to include more context.
# SSO with Microsoft Entra ID
Source: https://docs.mangrovesystems.com/account-admin/single-sign-on
Set up single sign-on so your team reaches Mangrove with their existing Microsoft Entra ID (Azure AD) work account.
Single sign-on (SSO) lets your team into Mangrove with the Microsoft work account they already have, so carbon accounting sits behind the same identity provider as the rest of your systems. Mangrove supports **Microsoft Entra ID** (formerly Azure AD), over OpenID Connect.
Your Mangrove account team sets it up. Here is how it goes.
## How setup works
The **email domains** you want to claim, your **Microsoft Entra tenant ID**, and the two choices in the next section. The full list is below.
Mangrove verifies each domain you own. There is no DNS record for you to add and nothing to configure inside your tenant.
Mangrove sets up the connection with the provisioning and password options you chose.
Nothing changes for your users until we confirm the connection is enabled. Sign-in switches over at that point.
## What to send your account team
To start, send them:
1. The **email domains** you want to claim, and confirmation that you own them. Mangrove verifies each one; there is no DNS record for you to add.
2. Your **Microsoft Entra tenant ID**. Find it in the Microsoft Entra admin centre under **Overview**, or at [Microsoft's instructions for locating your tenant ID](https://learn.microsoft.com/en-us/entra/fundamentals/how-to-find-tenant).
3. Whether new users should be **created automatically or by invitation only**. See below.
4. The **default role** for automatically created users, if you choose automatic. See [User roles](/account-admin/user-roles).
5. Whether the sign-in page should still offer a **password option**.
Mangrove does not need anything created inside your tenant. Depending on your consent settings, the first person to sign in may be prompted to consent, or an administrator may need to approve it once for the organization.
## The three decisions
### Which domains you claim
A connection covers the email domains your organization owns. A domain can be claimed by only one organization across Mangrove, so if a subsidiary or an acquired company has already claimed one you want, raise it with your account team rather than assuming it is free. Public mailbox domains such as `gmail.com` and `outlook.com` cannot be claimed.
### Whether accounts are created automatically
By default they are not. **An invitation is still required**, so on day one the only people who can sign in are those who already have access to your Mangrove account, plus anyone holding an invitation to it.
Ask your account team to turn on automatic creation and anyone in your directory who signs in gets an account, with the default role you chose. **That automatic role can never be Admin.** An invitation can still carry any role, including Admin, because a person decided to send it.
Turn it on where directory membership already means somebody should have access to your carbon data. Leave it off where it does not.
### Whether the sign-in page still offers a password
A connection can be **enforced**, which removes the "sign in with a password instead" option from the sign-in page.
## What changes for your users
The sign-in page asks for an email address first.
* **An address at a claimed domain** goes on to Microsoft and comes back signed in.
* **Any other address** gets the password field, with the address already filled in.
Signing in for the first time links a person's Microsoft identity to their existing Mangrove user. They keep their access and their history, and an unconfirmed email address is confirmed for them.
Three sign-ins are refused rather than linked, which is what your helpdesk will hear about:
| The user says | What happened | What to do |
| - | - | - |
| "It says my access was removed" | Their Mangrove access to your account was revoked. Signing in through your directory does not restore it. | Re-invite them. |
| "It says my account is deactivated" | Their Mangrove user was deactivated. Signing in never reactivates it. | Contact your account team. |
| "It says my identity is already linked" | They are signing in with a second Microsoft identity from a directory their Mangrove user is already linked to. | They add it from connected accounts while signed in, not from the sign-in page. |
## When somebody leaves
Revoke their access in Mangrove as part of offboarding. Mangrove does not sync with your directory, so removing them there does not remove them here.
## What Mangrove reads from your directory
Sign-in requests the `openid`, `profile` and `email` scopes only, so Mangrove sees the signed-in person's name and email address and nothing else.
# User roles
Source: https://docs.mangrovesystems.com/account-admin/user-roles
Below are the various user roles that can be assigned in Mangrove by Account Admins
| Role | Description | Associated Org |
| :- | :- | :- |
| Admin | Administrator with full system access. Can manage users, settings, and all data across the platform. | |
| Member | Standard user with access to core features. Can view and interact with assigned resources and participate in regular activities. | |
| Member - Read Only | Read only access to core features. Can view assigned resources. Cannot add, edit or remove any data. | |
| Customer | End client with access to their specific account information, orders, and support features. Can submit requests and view their transaction history. | Requires user to be associated with an existing Customer in the account |
# Show account settings
Source: https://docs.mangrovesystems.com/api-reference/account/get
GET /account
# Create an asset
Source: https://docs.mangrovesystems.com/api-reference/assets/create
POST /assets
Creates a new asset. At least one of `location_id` or `project_id` is required. An asset can belong to a location, to a project directly, or both. Neither is inferred from the other.
# Delete an asset
Source: https://docs.mangrovesystems.com/api-reference/assets/delete
DELETE /assets/{asset_id}
Deletes an asset. This action cannot be undone.
# Retrieve an asset
Source: https://docs.mangrovesystems.com/api-reference/assets/get
GET /assets/{asset_id}
Returns a specific asset by friendly ID.
# List assets
Source: https://docs.mangrovesystems.com/api-reference/assets/get-all
GET /assets
Returns all assets for the account. Assets are account-scoped. Use `filter[project_id][eq]` to narrow to a single project.
# Update an asset
Source: https://docs.mangrovesystems.com/api-reference/assets/patch
PATCH /assets/{asset_id}
Updates an asset. `custom_field_values` replaces the entire map. Merge your changes into the current values first, or unlisted keys are silently cleared. Pass an empty string for `location_id` or `project_id` to detach.
# Retrieve an async task
Source: https://docs.mangrovesystems.com/api-reference/async-tasks/get
GET /async_tasks/{task_id}
Returns the current status of a background task. The `task_id` is the one returned by whichever operation started the work, such as deleting a batch.
Polling needs a token with **Projects** read or write permission.
On a batch delete, `args`, `result` and `error_message` are returned only to the person who started the delete and to account admins. Anyone else polling the task, including a token with no person behind it, sees its state and progress without them.
# Authentication
Source: https://docs.mangrovesystems.com/api-reference/authentication
Authenticating with the Mangrove API.
For secure access to protected resources, requests to the Mangrove API need to be authenticated in order to identify the requesting party and their role.
## Prerequisites
To connect to the API, make sure you have the following prerequisites in place:
* **An API token.** Open **Account Admin → API**, click **New Token**, and give it a name, a user, an expiration, and its permissions. The token stays readable on that page afterwards, so you can copy it again later.
* **The permissions the endpoints you are calling need.** Permissions are granted by area, each with a read and a write tick box: Data Collection, Accounting and Reporting, Credit Inventory, Account Admin, Data Rules, and Compliance Calendar. Ticking an area grants the project read access it depends on. A token without the permission an endpoint needs gets a `403 Forbidden`.
## Token expiry
Every new token gets an expiration. The dropdown offers **30 days**, **60 days**, **90 days**, **180 days**, **1 year**, and **No expiration**, and defaults to 90 days.
A token's expiry cannot be changed after it is created. To move one out, create a replacement, switch your integration over, and delete the old token.
The **Expires** column on the API page is where you check. Within 14 days of expiry the date is highlighted and reads **Expires soon**. That highlight is the only warning, and only on that page, so put your own reminder in before a token running a production integration comes due.
A **No expiration** token never lapses and has to be rotated by hand. Prefer a dated token for anything you can rotate on a schedule.
Tokens created before expiry dates were introduced have none, and keep working.
## Making Authenticated API Requests
Include the token in the `Authorization` header of your HTTP request. Pass the raw token, with or without a `Bearer` prefix:
```
Authorization: YOUR_API_TOKEN
```
## Error Handling
An expired token returns `401` with a body naming the reason:
```json theme={null}
{
"errors": [
{ "message": "API token has expired" }
]
}
```
A token Mangrove does not recognise also returns `401`, with no body at all. Treat an empty `401` as a wrong or deleted token, and a `401` carrying that message as one that needs replacing.
A `403` means the token is valid and lacks the permission the endpoint needs. See [Status Codes](/api-reference/status-codes) for the full error format.
# Delete a batch
Source: https://docs.mangrovesystems.com/api-reference/batches/delete
DELETE /batches/{batch_id}
Queues a batch for deletion and returns the task tracking it. Deletion runs in the background, so a `202` means the work has started, not that the batch is gone. Poll `GET /async_tasks/{task_id}` with the returned `id` to follow it to completion.
Deletion blockers are still checked before anything is queued, so a batch that cannot be deleted returns `422` immediately and no task is created.
# List all evidences for a batch
Source: https://docs.mangrovesystems.com/api-reference/batches/evidences/get-all
GET /batches/{batch_id}/evidences
Returns all evidences associated with a batch through its model run.
# Retrieve a batch
Source: https://docs.mangrovesystems.com/api-reference/batches/get
GET /batches/{batch_id}
Returns a specific batch by ID, including underlying input data and allocation to reports. Unlike events and data points, the rule alert fields (`has_rule_alerts` and `rule_alerts`) are returned on this detail response only. The batch list response uses a lighter serializer that omits them.
# List all batches
Source: https://docs.mangrovesystems.com/api-reference/batches/get-all
GET /batches
Returns an array of all batches with filtering and sorting capabilities.
# Retrieve all batches on a ledger
Source: https://docs.mangrovesystems.com/api-reference/batches/get-ledger-batches
GET /ledgers/{ledger_id}/batches
Lists all batches for a specific ledger with filtering and sorting capabilities.
# Update a batch
Source: https://docs.mangrovesystems.com/api-reference/batches/update
PATCH /batches/{batch_id}
Updates a batch's tracking ID, the only editable field on a batch. The tracking ID is free text (commas and spaces are allowed, so a batch covering several deliveries can carry all of their numbers), and it is what the dashboard and reports show to identify the batch. Send an empty string to clear it. A batch whose tracking ID was computed from its model run cannot be renamed: a model that partitions by tracking ID (including composite partitions such as `tracking_id_and_feedstock`) stamps each batch from its own partitioning data points, and editing one returns 422. To change those, correct the tracking IDs on the underlying events and regenerate the batch.
# Update batch inputs
Source: https://docs.mangrovesystems.com/api-reference/batches/update-inputs
PATCH /ledgers/{ledger_id}/batches/{id}/update_inputs
Edits an existing batch's inputs, the API equivalent of the dashboard's edit-batch flow. Use it to re-attach data points, adjust upstream batch allocations, or skip data point types so they are recorded as `skipped_input` rather than left missing. Each array replaces the current set for that field (it is not merged). The batch's model runs are re-staged and recomputed asynchronously, so the response returns the batch immediately, before recomputation finishes. A batch that already has allocations cannot be edited.
# Generating batches via the API
Source: https://docs.mangrovesystems.com/api-reference/cookbooks/batch-generation
Use the Mangrove API to check required inputs, preview batch generation, and trigger batch creation for a ledger.
Batch generation is the process of running a model against collected data to produce batches on a ledger. This cookbook walks through the full workflow: checking what inputs are needed, previewing the results, and triggering generation.
You have a biochar project with production data collected over Q1 2025. You want to generate batches for the Net Carbon Removal ledger, which will run the linked model against your feedstock delivery and pyrolysis data to produce carbon removal batches.
Before you begin, make sure you have the following:
* A ledger with a model linked to it. You can check this via [GET /ledgers](/api-reference/ledgers/get-all) and looking for the `latest_model` field.
* An API token with Production Accounting read and write permissions.
* Data points (events with measurements) already collected for the time period you want to generate batches for.
List your ledgers and identify the one you want to generate batches for.
Endpoint: [GET /ledgers](/api-reference/ledgers/get-all)
```bash theme={null}
curl -X GET https://app.gomangrove.com/api/v1/ledgers \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
```json Response theme={null}
{
"data": [
{
"id": "lgr_abc123def456",
"name": "Net Carbon Removal",
"unit": "t",
"system_ledger_type": null,
"partition_type": "feedstock",
"cadence": "monthly",
"latest_model": {
"id": "mdl_xyz789",
"name": "Carbon Calculation Model"
},
"asset_category": null,
"created_at": "2025-01-15T10:00:00.000Z",
"updated_at": "2025-01-15T10:00:00.000Z"
}
]
}
```
Save the ledger `id` (e.g., `lgr_abc123def456`). Confirm `latest_model` is not null. Batch generation requires a linked model.
Before generating, check what data inputs the model requires. This helps you verify you have the right data collected.
Endpoint: [GET /ledgers/:id/required\_inputs](/api-reference/ledgers/required-inputs)
```bash theme={null}
curl -X GET https://app.gomangrove.com/api/v1/ledgers/lgr_abc123def456/required_inputs \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
```json Response theme={null}
{
"data": [
{
"id": 1,
"name": "Mass of Feedstock Delivery",
"slug": "feedstock-delivery-mass",
"unit": "U.S. ton",
"value_type": "number",
"category": "input",
"subcategory": "feedstock",
"is_manual_select": false,
"allocation_groups": [],
"output_of_ledgers": []
},
{
"id": 2,
"name": "Transport Distance",
"slug": "transport-distance",
"unit": "mile",
"value_type": "number",
"category": "input",
"subcategory": "transport",
"is_manual_select": false,
"allocation_groups": [],
"output_of_ledgers": []
}
]
}
```
Inputs with `is_manual_select: true` come from other ledgers and may require you to specify which data points to include via `data_point_ids` in the generation request.
Before committing to generation, preview how many batches will be created. This is especially useful when the model runs concurrently across multiple ledgers.
Endpoint: [POST /ledgers/:id/generate\_preview](/api-reference/ledgers/generate-preview)
```bash theme={null}
curl -X POST https://app.gomangrove.com/api/v1/ledgers/lgr_abc123def456/generate_preview \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"start_time": "2025-01-01T00:00:00Z",
"end_time": "2025-03-31T23:59:59Z"
}'
```
```json Response theme={null}
{
"data": [
{
"id": "lgr_abc123def456",
"name": "Net Carbon Removal",
"count": 3
}
]
}
```
The response shows how many batches will be generated per ledger. If the model runs concurrently with other models, you'll see multiple entries.
Once you're satisfied with the preview, trigger the actual generation. This is an **async operation**: it returns immediately with a task ID that you can poll for progress.
Endpoint: [POST /ledgers/:id/batch\_generations](/api-reference/ledgers/batch-generations)
```bash theme={null}
curl -X POST https://app.gomangrove.com/api/v1/ledgers/lgr_abc123def456/batch_generations \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"start_time": "2025-01-01T00:00:00Z",
"end_time": "2025-03-31T23:59:59Z"
}'
```
```json Response (202 Accepted) theme={null}
{
"id": 456,
"type": "ProductionAccounting::BatchGeneration",
"state": "pending",
"progress": 0,
"created_at": "2025-04-01T12:00:00.000Z"
}
```
Save the task `id` from the response to check on progress.
**Optional parameters:**
* `data_point_ids`: Array of specific data point friendly IDs to include. If omitted, data points are auto-discovered for the time range. Required when the model partitions by asset (the `asset` and `tracking_id_and_asset` strategies), which does not auto-discover.
* `batch_amounts`: Array of objects `{batch_id, primary_output_amount, allocation_group}` for specifying custom amounts on existing batches. On a partitioned model, `primary_output_amount` is the total for the generation, divided evenly across the partitions that actually run. The full amount is drawn either way, so running fewer partitions gives each a larger share rather than drawing less from the source batch.
* `skipped_data_point_type_ids`: Array of data point type IDs to exclude from generation.
* `generate_concurrent_ledgers`: Boolean, default `false`. Set it to `true` to generate the ledger's concurrent (sibling) ledgers in the same run. Only the requested ledger generates otherwise.
Use the async task endpoint to check on the generation progress.
Endpoint: [GET /async\_tasks/:id](/api-reference/async-tasks/get)
```bash theme={null}
curl -X GET https://app.gomangrove.com/api/v1/async_tasks/456 \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
```json Response (in progress) theme={null}
{
"id": 456,
"type": "ProductionAccounting::BatchGeneration",
"state": "running",
"progress": 50,
"progress_description": "Processing batch 2 of 3",
"started_at": "2025-04-01T12:00:01.000Z",
"finished_at": null,
"error_message": null,
"result": null,
"args": {
"project_id": 123,
"ledger_id": 456
}
}
```
```json Response (complete) theme={null}
{
"id": 456,
"type": "ProductionAccounting::BatchGeneration",
"state": "complete",
"progress": 100,
"progress_description": null,
"started_at": "2025-04-01T12:00:01.000Z",
"finished_at": "2025-04-01T12:00:15.000Z",
"error_message": null,
"result": null,
"args": {
"project_id": 123,
"ledger_id": 456
}
}
```
Poll until `state` is `"complete"` or `"failed"`. A reasonable polling interval is every 2-5 seconds.
Once the task completes, list the batches on the ledger to confirm they were created.
Endpoint: [GET /ledgers/:id/batches](/api-reference/batches/get-ledger-batches)
```bash theme={null}
curl -X GET "https://app.gomangrove.com/api/v1/ledgers/lgr_abc123def456/batches?sort=-created_at" \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
```json Response theme={null}
{
"data": [
{
"id": "bat_newbatch001",
"tracking_id": "Jan-2025-001",
"start_time": "2025-01-01T00:00:00.000Z",
"end_time": "2025-01-31T23:59:59.000Z",
"state": "complete",
"created_at": "2025-04-01T12:00:10.000Z",
"primary_output": {
"id": 1,
"name": "Net Carbon Removal",
"unit": "t",
"value": 33.5
},
"ledger_balance": 33.5
}
]
}
```
## Async task states
The `state` field on async tasks follows this lifecycle:
| State | Description |
| - | - |
| `pending` | Task created, waiting to be picked up |
| `running` | Task is actively processing |
| `complete` | Task finished successfully |
| `failed` | Task encountered an error: check `error_message` |
The async task pattern is also used by [report submission](/api-reference/reports/submit). After submitting a report to a registry, you'll receive an `async_task_id` that follows the same polling workflow.
# Dismissing many rule alerts at once
Source: https://docs.mangrovesystems.com/api-reference/cookbooks/bulk-dismiss-alerts
Find and dismiss data rule alerts in bulk using the Mangrove API.
This cookbook explains how to dismiss many data rule alerts in a single API call, with one shared reason.
A data rule has flagged dozens of records. For example, a "Negative Temp Alert" firing on a winter's worth of ambient-temperature data points. After reviewing them with your site engineer, you've confirmed they're expected and want to dismiss them all at once with a single reason, while preserving the audit trail.
Dismissing an alert only changes how it is surfaced. It never edits the underlying data point or model run. If the record is recalculated and the rule still matches, a fresh alert can be raised. See [Managing alerts](/data-rules/manage-alerts) for the full alert lifecycle.
## How it works
Every rule alert is backed by a **rule result** with its own friendly ID:
| Result type | ID prefix | Raised by |
| - | - | - |
| Data point rule result | `dpres_` | Data Points rules |
| Model run rule result | `mrres_` | Batch Calculation rules |
The [Bulk dismiss rule results](/api-reference/data-rules/bulk-dismiss) endpoint takes a list of these IDs plus one `reason`, and dismisses them in a single transaction. You can mix `dpres_` and `mrres_` IDs in the same request.
Pass **rule-result** IDs (`dpres_...`, `mrres_...`), not data point (`in_...`), event (`evt_...`), or model run (`mr_...`) IDs. The rule-result ID is the `id` on each entry in a record's `rule_results` array.
The call is **all-or-nothing**: if the reason is blank, the list is empty, or any ID is unknown to the project or points at a *passing* result, nothing is dismissed and the API returns `422`. Already-dismissed results are a no-op, so retrying a request is safe.
A single call accepts at most **1000** rule-result IDs, counted after duplicates are removed. Split a larger triage into successive calls; the examples below do this for you. Each call is its own transaction, so a failure part-way through leaves earlier chunks dismissed.
## Dismissing alerts with code
Before starting, make sure you have:
* A Mangrove API token with **Data Rules** read permission to list the results and write permission to dismiss them (the endpoints return `403` otherwise)
* The project ID containing the alerts
List the results for the rule you want to triage and filter to the alerts (`status=fail`), then read the `id` of each failing result.
```bash curl theme={null}
curl "https://app.gomangrove.com/api/v1/projects/prj_W55PYc6xVeXb2iuT/rules/rule_Qb3k9TzdL0pWnXyz/results?status=fail&page_size=100" \
-H "Authorization: YOUR_API_TOKEN"
```
Each entry's `id` (e.g. `dpres_8XF1QtGs79oX9A6k`) is what you collect.
Re-list right before dismissing. Recreating or re-saving a rule regenerates its rule-result friendly IDs, so IDs cached from an earlier session can become "Unknown result ids" (`422`). Always work from a fresh list.
Send up to 1000 IDs and a single reason to the bulk dismiss endpoint.
```bash curl theme={null}
curl -X POST "https://app.gomangrove.com/api/v1/projects/prj_W55PYc6xVeXb2iuT/rule_results/bulk_dismiss" \
-H "Authorization: YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"result_ids": [
"dpres_8XF1QtGs79oX9A6k",
"dpres_7nxaDUf65c36v6mz",
"mrres_AIKqFEtr5OpAqram"
],
"reason": "Reviewed with site engineer: sensor recalibrated, these values are expected."
}'
```
The response returns every targeted result with its final state, so dismissed entries come back with `status: dismissed` and the `dismissal_reason`, `dismissed_by`, and `dismissed_at` fields populated.
## End-to-end example
This script lists a rule's failing results, collects their IDs, and dismisses them in chunks of 1000.
```python Python theme={null}
import requests
API_TOKEN = "YOUR_API_TOKEN"
BASE_URL = "https://app.gomangrove.com/api/v1"
PROJECT_ID = "prj_W55PYc6xVeXb2iuT"
RULE_ID = "rule_Qb3k9TzdL0pWnXyz"
REASON = "Reviewed with site engineer: sensor recalibrated, these values are expected."
MAX_PER_CALL = 1000 # API limit on result_ids per request
headers = {
"Authorization": API_TOKEN,
"Content-Type": "application/json",
}
# 1. Collect the failing rule-result IDs (re-list right before dismissing).
result_ids = []
page = 1
while True:
resp = requests.get(
f"{BASE_URL}/projects/{PROJECT_ID}/rules/{RULE_ID}/results",
headers=headers,
params={"status": "fail", "page": page, "page_size": 100},
)
resp.raise_for_status()
body = resp.json()
result_ids += [r["id"] for r in body["data"]]
if page >= body["info"]["total_pages"]:
break
page += 1
if not result_ids:
print("No alerts to dismiss.")
else:
# 2. Dismiss them, at most MAX_PER_CALL at a time.
total = 0
for start in range(0, len(result_ids), MAX_PER_CALL):
chunk = result_ids[start:start + MAX_PER_CALL]
resp = requests.post(
f"{BASE_URL}/projects/{PROJECT_ID}/rule_results/bulk_dismiss",
headers=headers,
json={"result_ids": chunk, "reason": REASON},
)
resp.raise_for_status()
total += len(resp.json()["data"])
print(f"Dismissed {total} results.")
```
```javascript Node.js theme={null}
const API_TOKEN = 'YOUR_API_TOKEN';
const BASE_URL = 'https://app.gomangrove.com/api/v1';
const PROJECT_ID = 'prj_W55PYc6xVeXb2iuT';
const RULE_ID = 'rule_Qb3k9TzdL0pWnXyz';
const REASON = 'Reviewed with site engineer: sensor recalibrated, these values are expected.';
const MAX_PER_CALL = 1000; // API limit on result_ids per request
const headers = {
Authorization: API_TOKEN,
'Content-Type': 'application/json',
};
async function dismissAlerts() {
// 1. Collect the failing rule-result IDs (re-list right before dismissing).
const resultIds = [];
let page = 1;
while (true) {
const params = new URLSearchParams({ status: 'fail', page, page_size: '100' });
const res = await fetch(
`${BASE_URL}/projects/${PROJECT_ID}/rules/${RULE_ID}/results?${params}`,
{ headers }
);
if (!res.ok) throw new Error(`List failed: ${res.status}`);
const body = await res.json();
resultIds.push(...body.data.map((r) => r.id));
if (page >= body.info.total_pages) break;
page += 1;
}
if (resultIds.length === 0) {
console.log('No alerts to dismiss.');
return;
}
// 2. Dismiss them, at most MAX_PER_CALL at a time.
let total = 0;
for (let start = 0; start < resultIds.length; start += MAX_PER_CALL) {
const chunk = resultIds.slice(start, start + MAX_PER_CALL);
const res = await fetch(
`${BASE_URL}/projects/${PROJECT_ID}/rule_results/bulk_dismiss`,
{
method: 'POST',
headers,
body: JSON.stringify({ result_ids: chunk, reason: REASON }),
}
);
if (!res.ok) throw new Error(`Dismiss failed: ${res.status} ${await res.text()}`);
const { data } = await res.json();
total += data.length;
}
console.log(`Dismissed ${total} results.`);
}
dismissAlerts();
```
## Troubleshooting
| Response | Meaning | What to do |
| - | - | - |
| `403 You are not authorized to perform this action` | The token does not have **Data Rules** write permission. | Tick **Data Rules** for read and write on the token in **Account Admin → API**. |
| `404 record not found` | The project ID is wrong, or the route isn't reachable for this token. | Double-check the `project_id` in the path and that the token is scoped to that project. |
| `422 Unknown result ids: ...` | One or more IDs don't belong to the project, often stale IDs after a rule was recreated. | Re-list the rule's results and rebuild the ID set from the fresh response. |
| `422 Cannot dismiss passing results: ...` | The batch includes a result whose status is `pass`. | Filter to `status=fail` (or `dismissed`) before collecting IDs. |
| `422 Dismissal reason can't be blank` | The `reason` was empty or whitespace. | Provide a non-empty reason: it's stored on the audit trail. |
| `422 Cannot dismiss more than 1000 results at once` | The request carried more than 1000 IDs. | Split the list into chunks of 1000 and send them in sequence. |
The split between `404` and `422` is diagnostic: a `404` points at the path or record lookup (wrong `project_id`, route not reachable), while a `422` is business validation (blank reason, empty list, unknown or passing IDs). If a previously working call starts returning `404`, suspect a routing or scoping issue rather than your payload.
# Attaching evidence to multiple events
Source: https://docs.mangrovesystems.com/api-reference/cookbooks/bulk-evidence-upload
Upload evidence files to multiple Mangrove events using the API.
This cookbook explains how to upload an evidence file to multiple Mangrove events using the API.
You have a single evidence file (e.g., a meter reading export) that needs to be attached to multiple events in your Mangrove project. Instead of uploading manually to each event, you can automate this by iterating over your event IDs and calling the API.
### Uploading Evidence with Code
Before starting, make sure you have:
* A Mangrove API token with Data Collection permissions (read and write access)
* A list of event IDs you want to attach evidence to
* Your evidence files
* You can use a tool like [Postman](https://www.postman.com/) to make the API calls.
The API accepts evidence files as base64-encoded strings.
Open Terminal and run:
```bash theme={null}
base64 -i path/to/your/file.csv | pbcopy
```
This converts your file into base64 and copies it to your clipboard.
Use the [Create evidence on event](/api-reference/evidence/create) endpoint to attach evidence to each event.
Here's an example request to upload evidence to one event:
```bash curl theme={null}
curl -X POST "https://app.gomangrove.com/api/v1/events/evt_abc123/evidence" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"evidences": [
{
"type": "file",
"data": {
"file": {
"name": "meter_readings.csv",
"type": "text/csv",
"base64": "YOUR_BASE64_STRING"
}
}
}
]
}'
```
To upload to multiple events, iterate over your list of event IDs. Here are examples in different languages:
```bash Shell Script theme={null}
#!/bin/bash
API_TOKEN="YOUR_API_TOKEN"
FILE_BASE64=$(base64 -i evidence.csv)
EVENT_IDS=("evt_001" "evt_002" "evt_003")
for EVENT_ID in "${EVENT_IDS[@]}"; do
curl -X POST "https://app.gomangrove.com/api/v1/events/${EVENT_ID}/evidence" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"evidences\": [{
\"type\": \"file\",
\"data\": {
\"file\": {
\"name\": \"evidence.csv\",
\"type\": \"text/csv\",
\"base64\": \"$FILE_BASE64\"
}
}
}]
}"
echo "Uploaded to $EVENT_ID"
done
```
```python Python theme={null}
import requests
import base64
API_TOKEN = "YOUR_API_TOKEN"
EVENT_IDS = ["evt_001", "evt_002", "evt_003"]
# Read and encode the file
with open("evidence.csv", "rb") as f:
file_base64 = base64.b64encode(f.read()).decode()
headers = {
"Authorization": f"Bearer {API_TOKEN}",
"Content-Type": "application/json"
}
for event_id in EVENT_IDS:
response = requests.post(
f"https://app.gomangrove.com/api/v1/events/{event_id}/evidence",
headers=headers,
json={
"evidences": [{
"type": "file",
"data": {
"file": {
"name": "evidence.csv",
"type": "text/csv",
"base64": file_base64
}
}
}]
}
)
print(f"{event_id}: {response.status_code}")
```
```javascript Node.js theme={null}
const fs = require('fs');
const API_TOKEN = 'YOUR_API_TOKEN';
const EVENT_IDS = ['evt_001', 'evt_002', 'evt_003'];
const fileBase64 = fs.readFileSync('evidence.csv').toString('base64');
async function uploadEvidence() {
for (const eventId of EVENT_IDS) {
const response = await fetch(
`https://app.gomangrove.com/api/v1/events/${eventId}/evidence`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${API_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
evidences: [{
type: 'file',
data: {
file: {
name: 'evidence.csv',
type: 'text/csv',
base64: fileBase64
}
}
}]
})
}
);
console.log(`${eventId}: ${response.status}`);
}
}
uploadEvidence();
```
If you're attaching the same file to multiple events, encode it once and reuse the base64 string for each request.
### Using Postman
You can also use Postman's Collection Runner to upload evidence to multiple events without writing code.
1. Open Postman
2. Create a new Collection (e.g., "Mangrove Evidence Upload")
3. Inside the collection, click **Add Request**
**URL:**
```
POST https://app.gomangrove.com/api/v1/events/{{event_id}}/evidence
```
**Authorization tab:**
* Set **Type** to `Bearer Token`
* Paste your Mangrove API token
**Headers tab:**
| Key | Value |
| - | - |
| Content-Type | application/json |
**Body tab:**
* Select **raw** and set format to **JSON**
* Paste the following:
```json theme={null}
{
"evidences": [
{
"type": "file",
"data": {
"file": {
"name": "{{file_name}}",
"type": "text/csv",
"base64": "{{file_base64}}"
}
}
}
]
}
```
The `{{event_id}}`, `{{file_name}}`, and `{{file_base64}}` variables will be populated from your CSV file.
Open Terminal (macOS) and run:
```bash theme={null}
base64 -i path/to/your/file.csv | pbcopy
```
This converts your file to base64 and copies it to your clipboard.
Create a file called `events.csv` with these columns:
| Column | Description |
| - | - |
| event\_id | The event ID to attach evidence to |
| file\_name | File name displayed in Mangrove |
| file\_base64 | Base64 string of your evidence file |
```csv Example theme={null}
event_id,file_name,file_base64
evt_001,evidence.csv,AAAABBBB...
evt_002,evidence.csv,AAAABBBB...
evt_003,evidence.csv,AAAABBBB...
```
If attaching the same file to multiple events, reuse the same base64 value across rows.
1. In Postman, open your Collection
2. Click **Run**
3. Upload `events.csv` as the data file
4. Click **Run**
Postman will send one request per row in your CSV file. The number of successful responses should match the number of rows.
# Correcting many values at once
Source: https://docs.mangrovesystems.com/api-reference/cookbooks/substitute-values
Write a substitute rule through the Mangrove API to correct invalid or missing data points automatically, and read back what it changed.
This cookbook explains how to correct data at scale. You write a rule that describes which data points are wrong, and the methods Mangrove should try in order to work out a replacement. The rule then corrects every matching data point in its effective period and keeps correcting new ones as they arrive. A rule set to run on demand corrects a period only when you evaluate it.
An ambient temperature probe at one site drops out intermittently, so data points arrive as `0`. A season of production data is affected, so correcting each value by hand is not practical, and the dropouts will keep happening. You want every affected value replaced with the average of the preceding week, falling back to the last valid value when that week has nothing to average.
## Two ways to write a correction
| | Single value | Substitute rule |
| - | - | - |
| Endpoint | [Correct a data point value](/api-reference/corrections/set-value) | [Create a rule](/api-reference/data-rules/create) |
| Scope | One data point on one event | Every data point matching a condition |
| Applies to new data | No | Yes, while the rule is in its effective period, unless it runs on demand |
| Replacement value | You supply it | Mangrove derives it from a method chain |
| Recorded as | `source: api` | `source: rule`, with the rule named on the correction |
Both write to the same correction history on the data point, and both leave the original value readable. Use the single-value endpoint when you know the right answer for a specific data point. Use a substitute rule when the same class of problem recurs.
## Anatomy of a substitute rule
A substitute rule is an ordinary data rule with two extra fields. It works on data points that already exist on an event, including ones recorded without a value. A data point that ingestion never created at all has no row for a rule to act on, so use the single-value endpoint for those. The condition identifies the data points that need correcting, and the method chain says what to replace them with.
| Field | What it does |
| - | - |
| `rule_text` | The condition, in the [rule expression language](/data-rules/rule-expressions). A data point that matches is a data point Mangrove will try to correct. |
| `action` | `substitute`. The default is `alert`, which flags the data point and changes nothing. |
| `subject_slugs` | Exactly one data point type slug, naming which value the rule corrects. |
| `method_chain` | The ordered list of methods to try. |
| `target_type` | `data_point`. Batch calculation rules cannot substitute. |
| `requires_approval` | Optional. `true` holds each correction as a proposal for a person to approve before it is written. See [Deciding proposals over the API](#deciding-proposals-over-the-api). |
| `window_member_scope` | Optional, and only on a condition with a windowed aggregate. `all` extends the correction to every data point in the window, `repeats` to the repeated values in a stuck-sensor run. The default, `none`, corrects only the data point that fired. See [Repair a stuck sensor](#repair-a-stuck-sensor). |
Note three constraints on `subject_slugs`:
* It takes exactly one slug. A rule corrects one value, even when its condition reads several.
* The slug has to appear in `rule_text`.
* Its data point type has to be a Number type.
`alert_message` is optional on a substitute rule. When you leave it out, Mangrove supplies one, used on the data points the chain could not resolve.
## Substitution methods
Replaces the value with a fixed number. Always resolves, so no method after it can run. Where the condition reads a single value, Mangrove rejects a constant that would itself match it. On a condition reading several values it cannot make that check, so pick the constant with care.
Falls back to another data point type (`dpt`), copying its value for the same period. The rule builder calls this method **Cross fill**. The source has to be a Number type captured on an event. On the same event type, it reads the same event, and declines when the event carries more than one data point of that type. On another event type, it reads the one data point of that type at the same asset whose time range overlaps the value being corrected, and the source has to be in the same unit. It declines when there is none, or more than one.
Uses the nearest earlier valid value of the same type.
Uses the nearest later valid value of the same type.
Averages the same type's values across the window, rounded to five decimal places.
Takes the lowest value of the same type in the window.
Takes the highest value of the same type in the window.
Takes the chosen percentile of the same type's values in the window. `percentile` is a number from 0 to 100, so `90` is the 90th percentile and `50` the median. Where the rank falls between two values, the result is interpolated between them. Declines when the window holds fewer than two values.
Every method except `constant` takes an optional `slug` naming another data point type to read from, in place of the type being corrected. The source has to be a Number type, captured on an event, and in the same unit as the value being corrected, because values are copied with no conversion. `fallback_to_dpt` requires a `slug`, and on the same event type it accepts any unit.
The three trailing methods, `percentile` and both fills draw only on data points that pass the rule and have not themselves been corrected, so a chain never builds a value out of other substitutions. Both fills also decline where several data points tie at the winning timestamp, rather than let the correction depend on the order the data points were recorded in.
Without a `slug`, the trailing methods, `percentile` and both fills draw only on data points carrying the **same tracking ID** as the one being corrected. On a project that captures a tracking ID per delivery or per ticket, that usually leaves no candidates at all, and the rule alerts instead of correcting. If a rule corrects nothing and you expected it to, check whether tracking IDs are set on the events it reads.
Every method except `constant`, given another data point type in `slug`, reads that type's data points at the same asset instead, whatever their tracking ID.
Two limits apply. A fill looks back or forward at most 100 data points. A window normally draws on every qualifying data point in it, but when the rule's condition contains a windowed aggregate or a string comparison, the window is limited to the 1000 data points nearest the one being corrected.
`trailing_min`, `trailing_max` and the two fills copy an existing value exactly. `trailing_average` and `percentile` calculate a new one, rounded to five decimal places.
### Windows
The trailing methods and `percentile` take a `window` of `{ kind, value, unit, offset, extent }`.
By default neither kind reaches past the data point being corrected, so a replacement is derived from data that already existed when the value was captured. A `calendar` window with no offset is therefore its period **to date**, not the whole period. `backward_fill` reads later values, and so does a whole-period `calendar` window, described below.
| `kind` | Meaning | Units |
| - | - | - |
| `trailing` | The span immediately before the data point. Needs a positive `value`. `rolling` is accepted as the same thing here and also reads only backward; in a condition, `WITHIN ROLLING` reaches both ways. | `second`, `minute`, `hour`, `day`, `week`, `month`, `year` |
| `calendar` | The data point's own calendar period, up to the data point itself, or the whole period with `extent: whole_period`. `value` is optional and counts how many consecutive periods the window covers, defaulting to `1`. | `day`, `week`, `month`, `quarter`, `year` |
`offset` steps the window back whole periods, which is how you say "the period before this one" rather than a span trailing the value. It defaults to `0` and takes a non-negative integer.
On a `calendar` window, `offset: 1` is the previous bucket, and because that bucket has already closed it is used in full rather than clamped. A monthly average that should draw on last month rather than this one so far is `{ "kind": "calendar", "unit": "month", "offset": 1 }`.
A `calendar` window can span several periods. `value` counts them and `offset` places the newest, so `{ "kind": "calendar", "unit": "year", "offset": 1, "value": 2 }` covers the two complete years before the year the data point falls in. With `offset: 0` the newest period still ends at the data point itself, and the earlier ones in the span are complete.
A `calendar` window takes an `extent` of `to_date`, the default, or `whole_period`. `{ "kind": "calendar", "unit": "month", "extent": "whole_period" }` reads the whole month the data point falls in, including data points after it, so a gap is filled from the month's full average. `whole_period` is accepted only on a single period with no offset, and only on a rule whose `evaluation` is `on_demand`; anything else returns `422`. A data point whose period has not ended when the run starts is left uncorrected and fails the rule, with a message to evaluate it again after the period closes. No later method in the chain runs for it.
On a `trailing` window, an offset shifts the whole span back by its own length. `{ "kind": "trailing", "value": 7, "unit": "day", "offset": 1 }` is the seven days ending a week before the value, not the seven days immediately before it.
### How a chain resolves
Methods run in the order you list them and the first one to produce a value wins. A method that cannot produce one is skipped and the next is tried. A value that would match the rule's own condition is refused, and the chain carries on to the next method.
When no method resolves, nothing is written and the data point is flagged with an alert instead. This is the outcome `alert_message` covers.
Mangrove rejects a chain at creation when it repeats a method with the same parameters, or when a method follows a `constant`.
Two substitute rules matching the same data point correct nothing. Mangrove will not choose between competing rules, so where several fire on one data point it applies none of them and both report the data point as unresolved. The alert names the rules that collided, so start there. Before adding a rule, check that no existing rule already covers the same values, and if a rule's corrected count comes in lower than you expect, an overlap is the first thing to look for.
## Writing the rule
Before starting, make sure you have:
* A Mangrove API token with **Data Rules** write permission, and **Data Collection** read permission to read back what changed. Reverting a value needs **Data Collection** write as well.
* The project ID, and the slug of the data point type you are correcting
Post the condition, the subject slug, and the method chain together.
```bash curl theme={null}
curl -X POST "https://app.gomangrove.com/api/v1/projects/prj_W55PYc6xVeXb2iuT/rules" \
-H "Authorization: YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"rule": {
"name": "Fill ambient temperature dropouts",
"target_type": "data_point",
"action": "substitute",
"rule_text": "{{data-point.ambient-temperature-c}} = 0",
"subject_slugs": ["ambient-temperature-c"],
"method_chain": [
{
"method": "trailing_average",
"params": { "window": { "kind": "trailing", "value": 7, "unit": "day" } }
},
{ "method": "forward_fill" }
]
}
}'
```
A successful create returns `201` and the saved rule, including the friendly ID you will use to read its results.
Mangrove queues an evaluation as soon as the rule is saved, so the corrections land shortly after the call returns rather than during it. A rule created with `"evaluation": "on_demand"` is the exception: saving it evaluates nothing, and it runs only over a period you start with [Re-evaluate a rule over a period](/api-reference/rule-runs/create). A rule with no `effective_from` or `effective_to` covers the project's whole history and everything that arrives afterwards. Set either one to bound it, for example to start at a methodology change.
Track a long backfill by re-reading the rule's stats until `evaluated` stops climbing.
The rule's stats give you the count without paging through every record.
```bash curl theme={null}
curl "https://app.gomangrove.com/api/v1/projects/prj_W55PYc6xVeXb2iuT/rules/rule_Qb3k9TzdL0pWnXyz/stats" \
-H "Authorization: YOUR_API_TOKEN"
```
On a substitute rule, `corrected` counts the data points the chain resolved. `measured` counts everything else it evaluated, which includes both the data points that passed the condition and the ones the chain could not resolve, so it is not a clean success-versus-failure split.
On a substitute rule the two result statuses do not mean what they mean on an alert rule, and they do not mean what the app's filters of the same name mean either.
| `status` | On an alert rule | On a substitute rule |
| - | - | - |
| `fail` | The records it flagged | The records it fired on and did **not** correct, where the value still trips the condition |
| `pass` | The records that passed | Everything else: the records that never matched, the ones it corrected, and the ones it proposed a correction for that nobody has decided yet |
A fired correction is not an alert, so a successful one is recorded as a pass, and so is a proposal still waiting on approval (read `approval_state` to tell those apart). `status=fail` is therefore the data points that need attention:
```bash curl theme={null}
curl "https://app.gomangrove.com/api/v1/projects/prj_W55PYc6xVeXb2iuT/rules/rule_Qb3k9TzdL0pWnXyz/results?status=fail&page_size=100" \
-H "Authorization: YOUR_API_TOKEN"
```
Each of those carries a reason: the chain resolved nothing, the data point sits in a batch in a published report, someone reverted the correction, the write failed, or two substitute rules fired on the same data point.
To find the corrections instead, read the results and filter on the per-record `corrected` flag, which is true where this version wrote a replacement. There is no query parameter for it, so page through and filter client side. For the count alone, use `/stats`.
The app's **Evaluated Records** filter relabels the same two statuses: **Substituted** maps to `fail` and **Measured** to `pass`, the opposite way round from this endpoint. A count taken from the app will not match a count taken from the API unless you account for that.
A record can be both `corrected` and `fail`: the rule corrected it once, a later edit put it back in breach, and a version only ever writes one correction per data point. Widening the window or adding a further method to the chain usually clears most of the unresolved ones. Editing the rule mints a new version and re-evaluates.
Each correction is appended to its data point's history, which you can read per value.
```bash curl theme={null}
curl "https://app.gomangrove.com/api/v1/projects/prj_W55PYc6xVeXb2iuT/events/evt_DLnYvzbjSujNAvXE/data_points/ambient-temperature-c/corrections" \
-H "Authorization: YOUR_API_TOKEN"
```
A rule-written correction comes back with `source: rule` and the rule named in the `rule` object, alongside the `from_value` it replaced and the `method` that won the chain.
## Recipes: stuck sensor and held outlier
The example rule above corrects one value at a time from a fixed condition. The two recipes below use conditions that read a value against its own history, and settings that reach past the single value that fired. Each is a complete request body for [Create a rule](/api-reference/data-rules/create); swap in slugs that exist in your project. The conditions are explained in [Catching a stuck sensor](/data-rules/rule-expressions#catching-a-stuck-sensor) and [Spotting an outlier with ZSCORE](/data-rules/rule-expressions#spotting-an-outlier-with-zscore).
### Repair a stuck sensor
The condition is the stuck-sensor check from [Rule Expressions](/data-rules/rule-expressions#catching-a-stuck-sensor): the lowest and highest value over the trailing hour are the same, with at least three values behind it. What the API adds is `window_member_scope: repeats`, which corrects every repeated value in the window when the condition fires and leaves the first value of the run as measured. The fills and the three trailing methods leave the flat stretch out when they look for a source, so the replacement is never the stuck value itself.
```bash curl theme={null}
curl -X POST "https://app.gomangrove.com/api/v1/projects/prj_W55PYc6xVeXb2iuT/rules" \
-H "Authorization: YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"rule": {
"name": "Repair frozen flow meter",
"target_type": "data_point",
"action": "substitute",
"rule_text": "MIN({{data-point.flow-rate}} WITHIN TRAILING 1.hour) = MAX({{data-point.flow-rate}} WITHIN TRAILING 1.hour) AND COUNT({{data-point.flow-rate}} WITHIN TRAILING 1.hour) >= 3",
"window_member_scope": "repeats",
"subject_slugs": ["flow-rate"],
"method_chain": [
{
"method": "trailing_average",
"params": { "window": { "kind": "trailing", "value": 24, "unit": "hour" } }
}
]
}
}'
```
Before creating the rule, run the condition through the [preview endpoint](/api-reference/data-rules/preview). Its response carries `suggested_window_member_scope`: `repeats` for a `MIN = MAX` condition like this one, `all` for any other windowed condition. Send that value back as `window_member_scope`. Raise the `COUNT` to require more identical values before the rule fires. An alert rule with the same condition and `window_member_scope: repeats` flags the run instead of correcting it.
### Replace an outlier and hold it for approval
To catch a value that is inside its permitted range but out of character for the month, test it against the spread of the month's values. `ZSCORE` returns how many standard deviations the value sits from the window's average, so `NOT BETWEEN -2 AND 2` matches anything more than two standard deviations out in either direction. It always needs a window, and when the month holds fewer than two values, or they are all identical, it returns nothing and the rule stays quiet.
Because an outlier is sometimes real, this rule holds each correction for a person to approve. With `requires_approval: true`, a match becomes a proposal: the data point keeps its measured value, and nothing is written until someone approves it. The replacement here is the median of the previous complete month, which is what `percentile: 50` over a calendar window one period back means.
```bash curl theme={null}
curl -X POST "https://app.gomangrove.com/api/v1/projects/prj_W55PYc6xVeXb2iuT/rules" \
-H "Authorization: YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"rule": {
"name": "Methane content outliers",
"target_type": "data_point",
"action": "substitute",
"rule_text": "ZSCORE({{data-point.methane-content}} WITHIN CALENDAR MONTH) NOT BETWEEN -2 AND 2",
"requires_approval": true,
"subject_slugs": ["methane-content"],
"method_chain": [
{
"method": "percentile",
"params": { "percentile": 50, "window": { "kind": "calendar", "unit": "month", "offset": 1 } }
},
{ "method": "forward_fill" }
]
}
}'
```
Deciding the proposals this raises is covered in [Deciding proposals over the API](#deciding-proposals-over-the-api).
## Deciding proposals over the API
A rule created with `requires_approval: true` writes nothing on its own. Each match appears in the rule's [results](/api-reference/data-rules/results) as a `pass` with `approval_state` set to `pending`, and a `proposed_correction` object carrying the value the rule would write, the method that produced it, and how it arrived at the number. A data point with a pending proposal cannot go into a new batch until someone decides it.
List the rule's results filtered to `approval_state=pending`. The rule's own `pending_count` counts what is waiting across all of its active versions.
```bash curl theme={null}
curl "https://app.gomangrove.com/api/v1/projects/prj_W55PYc6xVeXb2iuT/rules/rule_Qb3k9TzdL0pWnXyz/results?approval_state=pending&page_size=50" \
-H "Authorization: YOUR_API_TOKEN"
```
Each pending entry looks like this, with the derivation trimmed:
```json theme={null}
{
"id": "dpres_AbCd1234567890Ef",
"status": "pass",
"entered_value": "0.0",
"approval_state": "pending",
"proposed_correction": {
"proposed_value": 61.24,
"method": "percentile",
"version": 1,
"proposed_at": "2026-09-03T08:15:00Z",
"derivation": { "input_count": 28 }
}
}
```
Approve writes the correction exactly as proposed. Reject needs a `reason`, which is stored against the result.
```bash curl theme={null}
curl -X POST "https://app.gomangrove.com/api/v1/projects/prj_W55PYc6xVeXb2iuT/rule_results/dpres_AbCd1234567890Ef/approve" \
-H "Authorization: YOUR_API_TOKEN"
curl -X POST "https://app.gomangrove.com/api/v1/projects/prj_W55PYc6xVeXb2iuT/rule_results/dpres_AbCd1234567890Ef/reject" \
-H "Authorization: YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "reason": "Meter was offline; the zero is real" }'
```
Both return the decided result. An approved one carries `approval_state: approved` and a null `proposed_correction`; a rejected one carries `approval_state: rejected` with `rejected_at`, `rejected_by` and `rejection_reason` filled in. A rejection is a different judgment from dismissing an alert, so the `dismissed_*` fields stay null on it.
To decide up to 100 proposed corrections in one call, send their IDs to [bulk approve](/api-reference/data-rules/bulk-approve) or [bulk reject](/api-reference/data-rules/bulk-reject), with one `reason` for a bulk reject. Each proposal is decided on its own: the response lists the decided results under `data`, and each one that could not be decided under `errors` with its ID and one of the codes below, so a `200` can still carry failures. That includes `not_approver`: a caller without approval rights gets a `200` with every ID under `errors`, not a `403`. An unknown ID fails the whole call with `404` and decides nothing.
```bash curl theme={null}
curl -X POST "https://app.gomangrove.com/api/v1/projects/prj_W55PYc6xVeXb2iuT/rule_results/bulk_approve" \
-H "Authorization: YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "result_ids": ["dpres_AbCd1234567890Ef", "dpres_EfGh1234567890Ij"] }'
```
A decision is final. Deciding a result again returns `422` with the code `not_pending`. The other codes a decision can return:
| Code | Status | Meaning |
| - | - | - |
| `reason_required` | `422` | Reject was called without a `reason`. |
| `not_approver` | `403` (in bulk, an entry in `errors` inside a `200`) | The token's user does not have write access on the account. |
| `stale_value` | `422` | The data point's value changed since the proposal was made, so the proposal no longer applies. |
| `stale_rule_version` | `422` | The rule was edited since the proposal was made. |
| `reported_value` | `422` | The data point is in a batch on a published report and can no longer be corrected. |
| `unreachable_proposal` | `422` | Another correction reached the data point first, so the measured value the proposal was made against is gone. The proposal is withdrawn. |
Turning `requires_approval` off on the rule withdraws every proposal still pending: nobody approved them, so those data points keep their measured values. Turning it on or off does not create a new rule version.
## Reading corrected values back
Every data point read carries its correction state, so you do not have to fetch the history to tell whether a value was changed.
| Field | Meaning |
| - | - |
| `substituted` | The value in effect is a correction. |
| `imputed` | The correction filled a data point that was missing. |
| `raw_value` | The original value. Null where none was recorded. |
| `substitution` | The correction now in effect, or null. |
On the correction itself, `origin` reads `substituted` where a value was changed, `imputed` where one was filled in, and `reverted` where the value was put back to its original.
A rule never corrects a data point that has gone into a batch in a published report. Those are skipped and reported as skipped, so a rule run over a period you have already reported changes less than its match count suggests.
A correction does not recalculate anything downstream. A batch that was already generated keeps the value it was generated with. Data rules do re-evaluate against the corrected value, so a correction can clear an alert or raise a new one.
## What is only in the app
Two parts of the substitution surface have no API equivalent:
* **Derivation evidence.** The stage-by-stage record of which methods were tried and why each was skipped shows in the event drawer. On a written correction the API returns the winning method.
* **Previewing a substitution.** The [preview endpoint](/api-reference/data-rules/preview) dry-runs the condition and reports which data points would match. It does not compute what the chain would produce.
## Reverting a correction
`DELETE` on the value endpoint restores the original value, the one ingestion delivered, however many corrections stand on it, and appends one `revert` entry to the history recording the jump from the value in force back to the original.
```bash curl theme={null}
curl -X DELETE "https://app.gomangrove.com/api/v1/projects/prj_W55PYc6xVeXb2iuT/events/evt_DLnYvzbjSujNAvXE/data_points/ambient-temperature-c/value" \
-H "Authorization: YOUR_API_TOKEN"
```
A revert cannot itself be reverted: once the latest entry is a revert, the value is already the original and a second call returns `422`. A correction applied after a revert can be reverted in turn. Reverting a value by hand also stops rules from correcting that value again.
To put back every value a rule corrected, call [Revert a rule's corrections](/api-reference/data-rules/revert-corrections). It removes the rule's corrections in the background, so each value reads as **Original** again rather than **Reverted**, and returns the number of corrections the rule still holds as `corrections_outstanding`. A correction inside a reported batch, or one somebody has corrected since, stays in place. The count includes corrections in reported batches, so it is an upper bound on how many will be removed.
To see when the revert finishes, [list the rule's runs](/api-reference/rule-runs/get-all). The revert shows up as a run with `trigger` set to `on_demand` whose `state` moves from `reversing` to `reversed`, and its `reversal_summary` counts what it removed as `corrections_deleted` and what it left in place as `refused`. Nothing else on the run marks it as a revert, so it reads the same as an **Evaluate a period** run that was later reverted. To take back what a single run wrote, [revert the run](/api-reference/rule-runs/reverse).
When you delete a rule, the values it corrected stay corrected, and each keeps the rule's name and ID in its history.
## Troubleshooting
Validation messages come back prefixed with the field they are about.
| Response | Meaning | What to do |
| - | - | - |
| `422 Method chain must list at least one method for substitute rules` | `action` is `substitute` with no methods. | Add at least one method, or set `action` to `alert`. |
| `422 Subject slugs must name exactly one data point to correct (got 2)` | More than one slug, or none. | Name the single value the rule corrects. |
| `422 Subject slugs must be data points the rule references (ambient-temperature-c)` | The slug does not appear in `rule_text`. | Correct one of the values the condition reads. |
| `422 Subject slugs must be number data points to be corrected (transport-mode)` | The slug points at text, a date, or a boolean. | Substitution derives numbers. Use an alert rule instead. |
| `422 Action substitute rules must target data points` | `target_type` is `model`. | Batch calculation rules can only alert. |
| `422 Method chain step 1 (percentile) percentile must be a number between 0 and 100` | The percentile is out of range or missing. | Send a number from 0 to 100 in `params.percentile`. |
| `422 Method chain step 1 (trailing_average) calendar window value must be a positive whole number of periods` | A calendar window's `value` is zero, negative, or fractional. | Leave `value` out for one period, or send a whole number of periods. |
| `422 Method chain step 1 (fallback_to_dpt) measures °F, but ambient-temperature-c measures °C` | The source `slug` is in another unit from the value being corrected. | Pick a source in the same unit. |
| `422 Method chain step 1 (constant) does not read a source data point` | A `constant` step carries a `slug`. | Remove the `slug` from that step. |
| `422` naming `window_member_scope` as requiring a windowed aggregate | The condition has no `WITHIN` clause, so there is no window to extend the correction to. | Add a window to the condition, or leave `window_member_scope` out. |
| `422` naming an existing rule | Another active rule has the same condition over an overlapping period. | Edit the existing rule rather than adding a second. |
| `403 You are not authorized to perform this action` | The token lacks the permission the endpoint needs. | Tick **Data Rules** for the rules endpoints, and **Data Collection** for the value endpoints, in **Account Admin → API**. |
# Sending time series data to Mangrove
Source: https://docs.mangrovesystems.com/api-reference/cookbooks/sync-events
Send well-formed event data, associated with the right locations and evidence, for easy review and MRV accounting.
One of the most common use cases for integrating with the Mangrove API is sending timeseries data from a frontline measurement system to your MRV environment in Mangrove for accounting and reporting. This cookbook walks you through the steps to do so.
We will use the example of a SCADA database pushing meter measurements of carbon capture data to a CCS project developer's own Mangrove account. This can happen on a daily basis, where the measurements collected throughout the day are sent to Mangrove in one scheduled push at the end of the day (midnight).
Before you begin, make sure you have the following:
* Mangrove project ID: The unique identifier for the project you're targeting.
* API Credentials: A valid API token (Bearer token).
* The token must have Data Collection permissions with both read and write access.
Each event type in a Mangrove project has a unique slug. These are required when posting events.
Endpoint to use: [GET event types](/api-reference/event-types/get-event-types)
`GET /api/v1/projects/{project_id}/event_types`
```bash Example theme={null}
curl -X GET https://app.gomangrove.com/api/v1/projects/abc123/event_types \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
```json Response theme={null}
{
"data": [
{
"slug": "co2-capture",
"name": "CO2 Capture measurement",
"updated_at": "2024-12-11T22:20:13.956Z",
"data_point_types": [
{
"slug": "high-conc-co2-outflow-mass-flow-rate",
"name": "High concentration CO₂ outflow mass flow rate",
"value_type": "number",
"unit": "g/s"
},
{
"slug": "high-conc-co2-outflow-co2-mass-fraction",
"name": "High concentration CO₂ outflow CO₂ mass fraction",
"value_type": "number",
"unit": "%"
},
{
"slug": "high-conc-co2-outflow-temperature",
"name": "High concentration CO₂ outflow temperature",
"value_type": "number",
"unit": "°C"
},
{
"slug": "high-conc-co2-outflow-meter-status",
"name": "CO2 Outflow Meter Status",
"value_type": "string",
"unit": null
}
],
"requires_locations": false
},
{
"slug": "co2-injection",
"name": "CO2 Injection Measurement",
"updated_at": "2024-12-11T22:20:14.259Z",
"data_point_types": [
{
"slug": "injected-co2-mass-flow-rate",
"name": "Injected CO₂ mass flow rate",
"value_type": "number",
"unit": "lb/min"
},
{
"slug": "injected-co2-co2-mass-fraction",
"name": "Injected CO₂ CO₂ mass fraction",
"value_type": "number",
"unit": "%"
},
{
"slug": "injected-co2-temperature",
"name": "Injected CO₂ temperature",
"value_type": "number",
"unit": "°C"
},
{
"slug": "injected-co2-meter-status",
"name": "Injected CO2 Meter Status",
"value_type": "string",
"unit": null
}
],
"requires_locations": false
}
...
]
}
```
Save the slugs (`co2-capture`,`co2-injection`) for your POST payload.
Events must be associated with a specific location ID.
Endpoint to use: [GET project locations](/api-reference/locations/get-all)
`GET /v1/projects/{project_id}/locations`
```bash Example theme={null}
curl -X GET https://app.gomangrove.com/api/v1/projects/abc123/locations \
-H "Authorization: Bearer YOUR_API_TOKEN"
```
```json Response theme={null}
{
"data": [
{
"id": "loc_abc123def456",
"lat": "29.6765",
"long": "-94.6505",
"name": "Injection Well 1",
"updated_at": "2025-01-15T09:00:00.000Z",
"address": {
...
}
},
...
]
}
```
With event type slugs and location IDs in hand, you can now POST your timeseries data as events.
Endpoint to use: [POST event](/api-reference/events/create)
`POST /v1/projects/{project_id}/events`
Headers:
```http Headers theme={null}
Content-Type: application/json
Authorization: Bearer YOUR_API_TOKEN
```
```json Example Payload theme={null}
{
"event": {
"event_type": "co2-injection",
"start_time": "2025-06-04T00:00:00.000Z",
"end_time": "2025-06-04T00:14:59.000Z",
"notes": "Sent from the site historian",
"locations": [
{"id": "loc_abc123def456"},
... // if 2 locations are included, Mangrove takes the first location in the array as origin, and the 2nd as destination location. This is most applicable to transport events
],
"data_points": [
{
"slug": "injected-co2-mass-flow-rate",
"value": 2.28746
},
{
"slug": "injected-co2-co2-mass-fraction",
"value": 96.7
},
{
"slug": "injected-co2-temperature",
"value": 66.34059
},
{
"slug": "injected-co2-meter-status",
"value": "ONLINE"
}
]
}
}
```
After events are created, you can associate evidence files (e.g., images, PDFs) with them.
This call must be made per event. Automate it by iterating over the list of event IDs created in Step 4.
Endpoint to use: [POST evidence on an event](/api-reference/evidence/create)
`POST /v1/events/{event_id}/evidence`
```bash Example theme={null}
curl -X POST https://app.gomangrove.com/api/v1/events/event-123/evidence \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-F "file=@photo.jpg"
```
```json Response theme={null}
{
"id": "evidence-xyz",
"file_name": "photo.jpg",
"mime_type": "image/jpeg"
}
```
# Correct many data point values
Source: https://docs.mangrovesystems.com/api-reference/corrections/batch
POST /projects/{project_id}/corrections/batch
Applies many corrections in one all-or-nothing request. Each item speaks the same shape as a single correction and addresses its data point by event ID and slug, so one call can span many events and event types.
If any item fails, nothing is persisted. The response still reports every item so you can find the bad one: failing items carry `status: "error"` with a message, and items that would have succeeded come back as `not_applied`. Fix the reported item and resend the whole batch.
A `not_applied` item still carries a populated `correction` object, describing the change that was rolled back. Its `id` refers to a record that was never persisted, so do not store it or use it in a later request.
# List a data point's corrections
Source: https://docs.mangrovesystems.com/api-reference/corrections/get-all
GET /projects/{project_id}/events/{event_id}/data_points/{slug}/corrections
Lists the corrections recorded on one data point, oldest first, giving the audit trail behind its current value. Reverts appear as their own entries with `method: revert`. Corrections written by a data rule carry the rule that produced them.
The response is paginated and carries the standard `info` envelope. Sort with `sort` on `id`, `created_at`, `stage` or `source`, and filter with `filter` on `id`, `stage`, `source` or `method`. See [Pagination](/api-reference/pagination).
# Revert a data point correction
Source: https://docs.mangrovesystems.com/api-reference/corrections/revert
DELETE /projects/{project_id}/events/{event_id}/data_points/{slug}/value
Restores a data point's original value, the one ingestion delivered, however many corrections stand on it. One `revert` entry is appended to the history recording the jump from the value in force back to the original; nothing is deleted.
Once a data point's latest entry is a revert, its value is already the original and a second call returns 422. A correction applied after a revert can be reverted in turn. A standing correction whose value happens to equal the original is still revertible.
Reverting does not recalculate anything downstream. A batch that was already generated keeps the value it was generated with.
# Correct a data point value
Source: https://docs.mangrovesystems.com/api-reference/corrections/set-value
PUT /projects/{project_id}/events/{event_id}/data_points/{slug}/value
Substitutes the value of one data point, addressed by its data point type slug within an event. Use it to replace a wrong value, or to set one where none was recorded. The server records the previous value as `from_value`, which is null when no value existed, and creates the data point on the event if it did not exist yet.
Each call appends to the data point's correction history, so the original value stays readable.
Setting a value does not recalculate anything downstream. A batch that was already generated keeps the value it was generated with. Data rules do re-evaluate against the corrected value, so a correction can clear an alert or raise a new one.
**Replays return 200, new corrections return 201.** A call is treated as a replay when the data point's latest correction has both the same `method` and the same value, in which case that existing correction is returned and nothing is written. Values are compared after normalization, so `"10.2"`, `10.2` and a stored `10.20` all match. Note that `method` participates in this check: retrying with a different `method` writes a second correction rather than matching the first.
# Create a custom field definition
Source: https://docs.mangrovesystems.com/api-reference/custom-field-definitions/create
POST /custom_field_definitions
Creates a custom field definition. Once created, `key`, `value_type`, and `object_type` are immutable. Values are set on the parent record (`custom_field_values` on assets, `field_values` on projects).
# Delete a custom field definition
Source: https://docs.mangrovesystems.com/api-reference/custom-field-definitions/delete
DELETE /custom_field_definitions/{id}
Deletes a custom field definition. Rejected with 422 if any record still holds a value for this definition's key.
# Retrieve a custom field definition
Source: https://docs.mangrovesystems.com/api-reference/custom-field-definitions/get
GET /custom_field_definitions/{id}
Returns a specific custom field definition by friendly ID.
# List custom field definitions
Source: https://docs.mangrovesystems.com/api-reference/custom-field-definitions/get-all
GET /custom_field_definitions
Returns custom field definitions for the account. Without `project_id`, only account-wide definitions are returned; pass `project_id` to also include definitions scoped to that project.
# Update a custom field definition
Source: https://docs.mangrovesystems.com/api-reference/custom-field-definitions/patch
PATCH /custom_field_definitions/{id}
Updates a custom field definition. `key`, `value_type`, and `object_type` are immutable. For enum definitions, `enumeration_options` replaces the full list. Include existing options you want to keep. A project-scoped definition can be widened to account-wide by clearing `project_id`; narrowing is not supported.
# Send Bulk CSV to a Project Data Feed
Source: https://docs.mangrovesystems.com/api-reference/data-feeds/send-bulk-csv
POST /data_feeds/{data_feed_friendly_id}
Uploads a CSV file to an ingestion Data Feed for processing. The Data Feed must have a "Endpoint" source configured. The file will be processed asynchronously through the pipeline's stages (collection, transformation, publication).
# Create a data point type
Source: https://docs.mangrovesystems.com/api-reference/data-point-types/create
POST /projects/{project_id}/data_point_types
Creates a new data point type for a project.
# Delete a data point type
Source: https://docs.mangrovesystems.com/api-reference/data-point-types/delete
DELETE /projects/{project_id}/data_point_types/{data_point_type_id}
Deletes a data point type from a project.
# Retrieve a data point type
Source: https://docs.mangrovesystems.com/api-reference/data-point-types/get
GET /projects/{project_id}/data_point_types/{data_point_type_id}
Returns a specific data point type by ID.
# List all data point types on a project
Source: https://docs.mangrovesystems.com/api-reference/data-point-types/get-all
GET /projects/{project_id}/data_point_types
Shows all configured data point types that a project can accept.
# Approve a proposed correction
Source: https://docs.mangrovesystems.com/api-reference/data-rules/approve
POST /projects/{project_id}/rule_results/{id}/approve
Writes the correction a substitute rule proposed, exactly as proposed: the recorded value, method and derivation are applied as they stand, and the data point's value changes. Only a result whose `approval_state` is `pending` can be approved, and a decision is final. Requires a token with Data Rules write permission.
# Approve proposed corrections in bulk
Source: https://docs.mangrovesystems.com/api-reference/data-rules/bulk-approve
POST /projects/{project_id}/rule_results/bulk_approve
Approves up to 100 proposed corrections in one call and writes each one, as [Approve a proposed correction](/api-reference/data-rules/approve) does for one. Each result is decided on its own, so one that cannot be approved is reported in `errors` and the rest still go through. A caller without approval rights gets a 200 with every ID in `errors` under the code `not_approver`, not a 403. Requires a token with Data Rules write permission.
# Bulk dismiss rule results
Source: https://docs.mangrovesystems.com/api-reference/data-rules/bulk-dismiss
POST /projects/{project_id}/rule_results/bulk_dismiss
Dismisses many data rule results (alerts) in a single request, across any rules in the project. Each result is identified by its rule-result friendly ID (`dpres_...` for data point results, `mrres_...` for model run results); data point and model run results can be mixed in one call. The same dismissal reason is applied to every result. The operation is all-or-nothing: if the reason is blank, the list is empty, more than 1000 IDs are supplied, or any ID is unknown to the project or points at a passing result, nothing is dismissed and a 422 is returned. Already-dismissed results are a no-op, so retries are idempotent. Requires a token with Data Rules write permission.
# Reject proposed corrections in bulk
Source: https://docs.mangrovesystems.com/api-reference/data-rules/bulk-reject
POST /projects/{project_id}/rule_results/bulk_reject
Rejects up to 100 proposed corrections in one call with one reason, as [Reject a proposed correction](/api-reference/data-rules/reject) does for one. Each result is decided on its own, so one that cannot be rejected is reported in `errors` and the rest still go through. A caller without approval rights gets a 200 with every ID in `errors` under the code `not_approver`, not a 403. Requires a token with Data Rules write permission.
# Create a rule
Source: https://docs.mangrovesystems.com/api-reference/data-rules/create
POST /projects/{project_id}/rules
Creates a data rule on the project. A rule either flags matching records with an alert, or corrects the matching value by working through a chain of substitution methods.
Evaluation over the rule's historical range is queued as soon as the rule is saved, so results and corrections land shortly after this call returns rather than during it.
# Delete a rule
Source: https://docs.mangrovesystems.com/api-reference/data-rules/delete
DELETE /projects/{project_id}/rules/{rule_id}
Deletes the rule along with its results and alerts. Corrections the rule wrote stay in place and keep the rule's name and ID in their history. To put those values back first, call [Revert a rule's corrections](/api-reference/data-rules/revert-corrections). A delete is refused with 422 while an evaluation or a revert is running for the rule.
# Retrieve a rule
Source: https://docs.mangrovesystems.com/api-reference/data-rules/get
GET /projects/{project_id}/rules/{rule_id}
# List rules
Source: https://docs.mangrovesystems.com/api-reference/data-rules/get-all
GET /projects/{project_id}/rules
Lists the data rules on a project, most recently created first. Requires a token with Data Rules read permission.
# Update a rule
Source: https://docs.mangrovesystems.com/api-reference/data-rules/patch
PATCH /projects/{project_id}/rules/{rule_id}
Updates a rule. Changing the condition, the effective period, the action, the method chain or `window_member_scope` mints a new version and queues a re-evaluation, except on an `on_demand` rule, and the response carries `backfill_enqueued` to say whether one started. Changing the name, the alert message, `requires_approval` or `evaluation` updates the rule in place. Switching `requires_approval` off withdraws every proposal still pending on the rule.
# Preview a rule
Source: https://docs.mangrovesystems.com/api-reference/data-rules/preview
POST /projects/{project_id}/rules/preview
Evaluates a condition against recent records and reports what would pass and what would fail, without saving anything. Use it to size an alert before committing to it.
A preview reports on the condition only. It does not compute the values a substitute rule's method chain would produce.
# Reject a proposed correction
Source: https://docs.mangrovesystems.com/api-reference/data-rules/reject
POST /projects/{project_id}/rule_results/{id}/reject
Closes a proposed correction without writing it. The data point keeps its measured value, and the reason is stored against the result as `rejection_reason`. Only a result whose `approval_state` is `pending` can be rejected, and a decision is final. Requires corrections to be enabled on the account and a token with Data Rules write permission.
# List a rule's results
Source: https://docs.mangrovesystems.com/api-reference/data-rules/results
GET /projects/{project_id}/rules/{rule_id}/results
Lists the per-record outcome of a rule, one entry per record it evaluated. Defaults to the rule's latest version.
# Revert a rule's corrections
Source: https://docs.mangrovesystems.com/api-reference/data-rules/revert-corrections
POST /projects/{project_id}/rules/{rule_id}/revert_corrections
Removes every correction the rule wrote, across all of its versions, and puts each value back to the one it replaced. The corrections are removed rather than reverted, so each value reads as original again, as it does after reverting a rule run. The call returns at once with the number of corrections the rule still holds, and the work runs in the background. When that number is 0, nothing runs.
A correction inside a reported batch, or one somebody has corrected since, is left in place.
To track the revert, list the rule's runs with [List a rule's runs](/api-reference/rule-runs/get-all). The revert appears there as a run with `trigger` set to `on_demand` and `state` set to `reversing`, which moves to `reversed` once it finishes. Its `reversal_summary` counts the corrections removed as `corrections_deleted` and the ones left in place as `refused`. Nothing else on the run marks it as a revert, so it reads the same as an Evaluate a period run that was later reverted.
# Get a rule's stats
Source: https://docs.mangrovesystems.com/api-reference/data-rules/stats
GET /projects/{project_id}/rules/{rule_id}/stats
Aggregate counts for a rule version, so you can size its effect without paging through every result.
A substitute rule also returns `action`, `corrected` and `measured`. On those rules `alerted` and `passed` carry the same counts as `corrected` and `measured` under their older names.
# Rule expression grammar
Source: https://docs.mangrovesystems.com/api-reference/data-rules/syntax
GET /rules/syntax
Returns the canonical grammar for the rule expression language: the variable forms, operators, aggregate functions, and window units a rule condition may use, with the validation error codes a condition can fail on. It also carries the catalog of substitution methods a `method_chain` may use, with the parameters each one needs. Use it to build an editor or to validate expressions before saving a rule. The response is a description of the language and does not depend on any project.
# Create a datapoint on an event
Source: https://docs.mangrovesystems.com/api-reference/datapoints/create
POST /projects/{project_id}/events/{event_id}/data_points
Adds a datapoint to an event. An event can be associated with multiple datapoints.
# The Datapoint object
Source: https://docs.mangrovesystems.com/api-reference/datapoints/datapoint
A datapoint represents a defined value to be used in a production accounting or quantification.
### The Datapoint Object
Unique ID of datapoint, generated by Mangrove
Slug identifier for the data point type. Data point types and their slugs are defined within the Mangrove instance
Display name of the data point type (e.g., `Total Ash Delivered (wet mass) U.S. ton`)
Value for the data point. The type must match the data point type's expected value type (`number`, `string`, or `boolean`)
Unit of measurement for the data point (e.g., `U.S. ton`, `mile`). `null` if the value type is not `number`
Status of the datapoint. Possible values: `complete`, `incomplete`, `pending`
* `id`
* `created_at`
* `updated_at`
* `status`
* `value`
### DataPointType Attributes
Unique ID of datapoint type, generated by Mangrove
Slug of the datapoint type. Datapoint types and their slugs are defined within the Mangrove instance.
Name of the datapoint type
`number`, `string`, `boolean`
`null` if value\_type is not `number`
Whether this is a static (system-defined) data point type or a custom data point type
ID of the event type this data point type is associated with
Timestamp when the data point type was created
Timestamp when the data point type was last updated
# Delete a datapoint
Source: https://docs.mangrovesystems.com/api-reference/datapoints/delete
DELETE /projects/{project_id}/events/{event_id}/data_points/{data_point_id}
Deletes a specific datapoint by ID.
# Retrieve a datapoint
Source: https://docs.mangrovesystems.com/api-reference/datapoints/get
GET /projects/{project_id}/events/{event_id}/data_points/{data_point_id}
Retrieves a specific datapoint by ID.
# List all datapoints on an event
Source: https://docs.mangrovesystems.com/api-reference/datapoints/get-all
GET /projects/{project_id}/events/{event_id}/data_points
Lists all datapoints on a specific event with filtering and sorting capabilities. Results are ordered by `start_time` ascending unless you send `sort`, and ties are broken by `id` ascending.
# Get data points for a project
Source: https://docs.mangrovesystems.com/api-reference/datapoints/get-project-datapoints
GET /projects/{project_id}/data_points
List data points across all events in a project. All query params are optional filters. `start_time` and `end_time` can each be sent as a plain parameter or in the `filter` form, but not both, which returns 400. Sending `slug` both ways with different values also returns 400. An unknown filter, or a comparator the filter does not support, returns 400 too. Results are ordered by `start_time` ascending unless you send `sort`, and ties are broken by `id` ascending, so pages stay stable while the matching data does not change.
# Update a datapoint
Source: https://docs.mangrovesystems.com/api-reference/datapoints/patch
PATCH /projects/{project_id}/events/{event_id}/data_points/{data_point_id}
Update a datapoint's value or status. A value that has never been substituted is edited in place. Editing a value that has already been substituted records a correction with method `manual_entry` and source `api`, and the original stays available as `raw_value`. To record a first edit as a substitution, use [Correct a data point value](/api-reference/corrections/set-value). Submitting the value the datapoint already holds changes nothing and records nothing. Filling an empty slot is an initial measurement, not a correction. A null or blank value is rejected; use DELETE to remove a datapoint. An event can hold multiple datapoints.
# Create an event type
Source: https://docs.mangrovesystems.com/api-reference/event-types/create
POST /projects/{project_id}/event_types
Creates a new event type for a project.
# Delete an event type
Source: https://docs.mangrovesystems.com/api-reference/event-types/delete
DELETE /projects/{project_id}/event_types/{event_type_id}
Deletes an event type.
# Retrieve an event type
Source: https://docs.mangrovesystems.com/api-reference/event-types/get
GET /projects/{project_id}/event_types/{event_type_id}
Returns a specific event type by ID.
# List event types on a project
Source: https://docs.mangrovesystems.com/api-reference/event-types/get-event-types
GET /projects/{project_id}/event_types
Shows all configured event types that a project can accept with filtering and sorting capabilities.
# Update an event type
Source: https://docs.mangrovesystems.com/api-reference/event-types/patch
PATCH /projects/{project_id}/event_types/{event_type_id}
Updates an existing event type.
# Create an event
Source: https://docs.mangrovesystems.com/api-reference/events/create
POST /projects/{project_id}/events
Creates events to capture ongoing operational data within a specific project boundary.
# Delete an event
Source: https://docs.mangrovesystems.com/api-reference/events/delete
DELETE /projects/{project_id}/events/{event_id}
Delete the event and its data points. Evidence files will not be deleted.
# The Event object
Source: https://docs.mangrovesystems.com/api-reference/events/event
An Event is an operational activity taking place within a project's boundaries.
### The Event object
Id of unique event, generated by Mangrove
Slug of the event type that this event represents. Event types and their slugs are defined within the Mangrove instance
Display name of the event type (e.g., `Biochar Delivery`)
Optional. Reference ID tracking a specific object as it moves through the project
Start time of event
End time of event
List of datapoints within the event. Each datapoint within an event has a slug and a value
List of locations associated with the event. Each unique location is associated with a Location in Mangrove
List of supporting evidence associated with an event. Each object includes the evidence according to the evidence\_type
* `id`
* `start_time`
* `end_time`
* `created_at`
* `updated_at`
* `status`
* `tracking_id`
* `friendly_id`
### EventType
Unique ID of EventType, generated by Mangrove
Slug of the event type. Event types and their slugs are defined within the Mangrove instance. Examples of standard event types include `capture_co2`, `loss_co2`, and`use_electricity`
Display name of the event type (e.g., `Biochar Delivery`, `Feedstock Delivery`)
Whether this is a static (system-defined) event type or a custom event type
List of datapoint types within the event. See [the Datapoint Object](/api-reference/datapoints/datapoint)
Timestamp when the event type was created
Timestamp when the event type was last updated
# Retrieve an event
Source: https://docs.mangrovesystems.com/api-reference/events/get
GET /projects/{project_id}/events/{event_id}
Retrieves a specific event by ID.
# List all events on a project
Source: https://docs.mangrovesystems.com/api-reference/events/get-all
GET /projects/{project_id}/events
Lists all events on a specific project with filtering and sorting capabilities.
The filters that address a record by friendly ID or slug (`event_type_id`, `slug`, `location_id`, `feedstock_id`, `asset_id`) accept only the `eq` comparator; any other returns 400. The ordinary column filters below are unaffected and still take the full comparator set.
# Update an event
Source: https://docs.mangrovesystems.com/api-reference/events/patch
PATCH /projects/{project_id}/events/{event_id}
Updates a specific event by ID. A data point value that has never been substituted is edited in place. Editing a value that has already been substituted records a correction with method `manual_entry`, and the original stays available as the data point's `raw_value`. To record a first edit as a substitution, use [Correct a data point value](/api-reference/corrections/set-value). Submitting the value a data point already holds changes nothing and records nothing. Filling an empty slot is an initial measurement, not a correction. Clearing an existing value with null or a blank string is rejected.
# Create evidence on event
Source: https://docs.mangrovesystems.com/api-reference/evidence/create
POST /projects/{project_id}/events/{event_id}/evidences
Adds evidence to an event. An event can be associated with multiple pieces of evidence.
# Delete an evidence
Source: https://docs.mangrovesystems.com/api-reference/evidence/delete
DELETE /projects/{project_id}/events/{event_id}/evidences/{evidence_id}
Deletes a specific piece of evidence by ID.
# The Evidence object
Source: https://docs.mangrovesystems.com/api-reference/evidence/evidence
An Evidence is a file or URL that provides supporting evidence for a specific event measured.
### The Evidence object
Unique ID of evidence, generated by Mangrove
Access URL for the evidence
Can be "file" or "url"
Display name of the evidence (e.g., `Receipt.pdf`, `Google Homepage`)
# Retrieve an evidence
Source: https://docs.mangrovesystems.com/api-reference/evidence/get
GET /projects/{project_id}/events/{event_id}/evidences/{evidence_id}
Retrieves a specific piece of evidence by ID.
# List all evidences on an event
Source: https://docs.mangrovesystems.com/api-reference/evidence/get-all
GET /projects/{project_id}/events/{event_id}/evidences
Lists all evidences on a specific project's event.
# Get export status
Source: https://docs.mangrovesystems.com/api-reference/exports/get
GET /exports/{export_id}
Returns the current status of an export. When completed, includes a temporary download URL valid for 24 hours.
# Data Exports
Source: https://docs.mangrovesystems.com/api-reference/exports/overview
Export large datasets as CSV or JSON files via the API.
Many list endpoints (e.g. `/projects`, `/events`) can export their full result as a file through the `response_mode` query parameter, without paging through it. [Supported Endpoints](#supported-endpoints) lists them.
## Response Modes
The `response_mode` query parameter controls the response format of a list endpoint:
| Value | Description |
| - | - |
| `default` | Standard paginated response |
| `summary` | Returns a `count` and 10 `samples`, no pagination |
| `summary_with_export` | Returns a summary and triggers an async CSV or JSON export |
Here's an example query string: `/api/v1/projects?response_mode=summary_with_export`.
## Export Format
The `export_format` query parameter controls the file format when using `summary_with_export`:
| Value | Description |
| - | - |
| `csv` | Comma-separated values (default) |
| `json` | JSON array of objects |
| `xlsx` | Excel workbook (events and data points) |
Here's an example: `/api/v1/projects?response_mode=summary_with_export&export_format=json`.
## Export Flow
When you request `response_mode=summary_with_export`, the response includes an `export` object:
```json json theme={null}
{
"count": 42,
"samples": [...],
"response_mode": "summary_with_export",
"export": {
"id": "exp_ABC123",
"status": "pending",
"status_url": "https://app.gomangrove.com/api/v1/exports/exp_ABC123"
}
}
```
Poll the `status_url` until `status` changes to `completed`. The response will then include a `download_url`:
```json json theme={null}
{
"id": "exp_ABC123",
"status": "completed",
"download_url": "https://...",
"file_size_bytes": 102400,
"format": "csv"
}
```
The `download_url` is a temporary URL valid for 24 hours.
## Supported Endpoints
The list endpoints below can produce a file with `response_mode=summary_with_export`:
| Endpoint | Resource |
| - | - |
| `GET /projects` | Projects |
| `GET /projects/:id/events` | Events |
| `GET /projects/:id/event_types` | Event Types |
| `GET /projects/:id/data_points` | Data Points |
| `GET /batches` | Batches |
| `GET /ledgers` | Ledgers |
| `GET /ledgers/:id/batches` | Batches |
| `GET /orders` | Orders |
| `GET /feedstocks` | Feedstocks |
| `GET /projects/:id/tracking_units` | Tracking Units |
| `GET /models` | Models |
| `GET /reports` | Reports |
`response_mode=summary` (count plus 10 samples) works on more list endpoints
than the ones above. On a list endpoint that cannot produce a file, such as data
point types, project locations, batch evidence or report maps,
`summary_with_export` returns `400`.
To export deliveries, call `POST /exports` with `resource_type: "Delivery"`.
Export creation is restricted to **account-level tokens**. Customer- and
partner-scoped tokens receive a `403` when requesting `summary_with_export`.
## Limits
* Max **5 concurrent exports** per account.
* Max **500 MB** file size per export.
* Download URLs expire after **24 hours**.
* Export records are cleaned up after **7 days**.
# List all feedstocks
Source: https://docs.mangrovesystems.com/api-reference/feedstocks/get-all
GET /feedstocks
Returns an array of all feedstocks across all projects for the authenticated account, with filtering and sorting capabilities.
# Create batch generation
Source: https://docs.mangrovesystems.com/api-reference/ledgers/batch-generations
POST /ledgers/{ledger_id}/batch_generations
Triggers an async batch generation job for the given time range.
# Create a ledger
Source: https://docs.mangrovesystems.com/api-reference/ledgers/create
POST /projects/{project_id}/ledgers
Creates a new ledger scoped to a project.
# Delete a ledger
Source: https://docs.mangrovesystems.com/api-reference/ledgers/delete
DELETE /ledgers/{ledger_id}
Deletes a ledger. Cannot delete system ledgers or ledgers with batches/model runs.
# Preview batch generation
Source: https://docs.mangrovesystems.com/api-reference/ledgers/generate-preview
POST /ledgers/{ledger_id}/generate_preview
Previews what batches would be created for the given time range. Does not create any batches.
# Retrieve a ledger
Source: https://docs.mangrovesystems.com/api-reference/ledgers/get
GET /ledgers/{ledger_id}
Returns a specific ledger by ID.
# List all ledgers
Source: https://docs.mangrovesystems.com/api-reference/ledgers/get-all
GET /ledgers
Returns an array of all ledgers with filtering and sorting capabilities.
# Update a ledger
Source: https://docs.mangrovesystems.com/api-reference/ledgers/patch
PATCH /ledgers/{ledger_id}
Updates a ledger. Cannot update system ledgers.
# Get required inputs for batch generation
Source: https://docs.mangrovesystems.com/api-reference/ledgers/required-inputs
GET /ledgers/{ledger_id}/required_inputs
Returns the required input data point types for batch generation on this ledger. Requires the ledger to have a model configured.
# Create a location
Source: https://docs.mangrovesystems.com/api-reference/locations/create
POST /projects/{project_id}/locations
Create a new location for the given project.
# Delete a location
Source: https://docs.mangrovesystems.com/api-reference/locations/delete
DELETE /projects/{project_id}/locations/{location_id}
Destroy a location by id.
# Retrieve a location
Source: https://docs.mangrovesystems.com/api-reference/locations/get
GET /projects/{project_id}/locations/{location_id}
Retrieves a specific location by id.
# All locations on a project
Source: https://docs.mangrovesystems.com/api-reference/locations/get-all
GET /projects/{project_id}/locations
List all locations of a given project.
# The Location object
Source: https://docs.mangrovesystems.com/api-reference/locations/location
Locations are physical places within a project's boundaries where project activity occurs.
### The Location object
Unique ID of location, generated by Mangrove
Name of the location
Latitude of the location
Longitude of the location
Address details for the location
Timestamp when the location was created
Timestamp when the location was last updated
# Update a location
Source: https://docs.mangrovesystems.com/api-reference/locations/patch
PATCH /projects/{project_id}/locations/{location_id}
Update a specific location by id.
# MCP Integration
Source: https://docs.mangrovesystems.com/api-reference/mcp-setup
Connect your Mangrove data to AI assistants using the Model Context Protocol
The Mangrove MCP server enables you to query your MRV data using natural language through AI assistants like Claude and Cursor. Instead of manually exporting data and building custom queries, you can ask questions conversationally and receive structured insights from your production, accounting, and inventory data.
## What is MCP?
The Model Context Protocol (MCP) is an open standard that enables AI assistants to securely connect to external data sources and APIs. The Mangrove MCP server implements this protocol, providing a bridge between your Mangrove account and MCP-enabled tools.
MCP connections are read-only and respect your existing Mangrove API permissions. All queries are authenticated using your Mangrove API key.
## Use Cases
The MCP integration is particularly useful for:
* **Ad-hoc reporting**: Query specific metrics without building custom dashboards
* **Data validation**: Quickly audit data completeness and identify gaps
* **Trend analysis**: Ask questions about patterns across time periods or locations
* **Cross-referencing**: Correlate data across events, batches, and locations
## Available Data
The MCP server provides access to all Mangrove API endpoints. You can query:
Projects, locations, feedstocks, event types, events, data points, and evidence files
Batches, ledger transactions, reports, models, and model runs
Orders, issuances, retirements, and transfers
Account settings, user roles, and custom fields
For detailed information about each endpoint, see the [API Reference](/api-reference/authentication) sections.
## Setup
### Prerequisites
To connect to the Mangrove MCP server, you'll need:
1. **Mangrove API Key**: Generate a read-only API key as an Account Admin in [API Settings](https://app.gomangrove.com/settings/api)
2. **MCP Auth Token**: Provided by your Mangrove account team
3. **MCP Server URL**: `https://mcp.gomangrove.com/mcp`
In Claude Desktop, open **Settings**, select the **Developer** tab, and click **Edit Config**. This opens `claude_desktop_config.json` in your default editor and creates the file if you don't have one yet.
On macOS, Settings sits in the Claude menu in your top menu bar, not inside the Claude window.
If your version of Claude Desktop has no Developer tab, open the file yourself:
* **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
* **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
```bash macOS theme={null}
touch ~/Library/Application\ Support/Claude/claude_desktop_config.json
open -e ~/Library/Application\ Support/Claude/claude_desktop_config.json
```
```powershell Windows theme={null}
notepad "$env:APPDATA\Claude\claude_desktop_config.json"
```
Add the following configuration to your `claude_desktop_config.json`:
```json theme={null}
{
"mcpServers": {
"mangrove": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://mcp.gomangrove.com/mcp",
"--transport",
"http-only",
"--header",
"Authorization:${AUTH_TOKEN}",
"--header",
"X-Mangrove-Token:${MANGROVE_API_TOKEN}"
],
"env": {
"AUTH_TOKEN": "Bearer YOUR_AUTH_TOKEN",
"MANGROVE_API_TOKEN": "YOUR_MANGROVE_API_KEY"
}
}
}
}
```
Your auth token and API key go in the `env` block, and nothing else needs to change:
* In `AUTH_TOKEN`, replace `YOUR_AUTH_TOKEN` with your MCP auth token, keeping the word `Bearer` and the space before it (`"Bearer abc123"`).
* In `MANGROVE_API_TOKEN`, replace `YOUR_MANGROVE_API_KEY` with your API key from Mangrove API Settings.
Leave `${AUTH_TOKEN}` and `${MANGROVE_API_TOKEN}` in `args` exactly as written. They read the values from the `env` block.
Close and reopen Claude Desktop to load the new configuration.
In Claude Desktop, you should now see the Mangrove MCP server available. Try a simple query:
"What projects do I have access to in Mangrove?"
Navigate to Cursor Settings > MCP
Click "Add Server" and enter the following configuration:
```json theme={null}
{
"name": "mangrove",
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://mcp.gomangrove.com/mcp",
"--transport",
"http-only",
"--header",
"Authorization:${AUTH_TOKEN}",
"--header",
"X-Mangrove-Token:${MANGROVE_API_TOKEN}"
],
"env": {
"AUTH_TOKEN": "Bearer YOUR_AUTH_TOKEN",
"MANGROVE_API_TOKEN": "YOUR_MANGROVE_API_KEY"
}
}
```
In `env`, replace `YOUR_AUTH_TOKEN` with your MCP auth token (keep `Bearer` and the space before it) and `YOUR_MANGROVE_API_KEY` with your API key. Leave `${AUTH_TOKEN}` and `${MANGROVE_API_TOKEN}` in `args` exactly as written.
Toggle the Mangrove MCP server to "enabled" in your Cursor settings.
Open a new chat in Cursor and ask:
"Show me all events from the last week for project \[your-project-id]"
VS Code has native MCP support via GitHub Copilot Chat (agent mode). You can configure the server at workspace level (`.vscode/mcp.json`, shareable with your team) or at user level (applies across all workspaces).
Open the Command Palette (`Cmd/Ctrl+Shift+P`) and run **MCP: Open User Configuration** to edit your personal `mcp.json`, or create `.vscode/mcp.json` in your workspace root to share with teammates.
Add the following to your `mcp.json`:
```json theme={null}
{
"servers": {
"mangrove": {
"type": "http",
"url": "https://mcp.gomangrove.com/mcp",
"headers": {
"Authorization": "Bearer ${input:mangrove-auth-token}",
"X-Mangrove-Token": "${input:mangrove-api-key}"
}
}
},
"inputs": [
{
"id": "mangrove-auth-token",
"type": "promptString",
"description": "Mangrove MCP auth token (from your Mangrove account team)",
"password": true
},
{
"id": "mangrove-api-key",
"type": "promptString",
"description": "Mangrove API key (from API Settings)",
"password": true
}
]
}
```
VS Code will prompt you for the auth token and API key the first time the server starts and store them securely.
Click the **Start** action above the `"mangrove"` entry in `mcp.json`, or open Copilot Chat, switch to **Agent** mode, and select the Mangrove tools from the tool picker.
In Copilot Chat agent mode, ask:
"What projects do I have access to in Mangrove?"
[Claude Code](https://claude.com/claude-code) is Anthropic's CLI agent. It can connect to the Mangrove MCP server directly using the `claude mcp add` command.
From any terminal, run:
```bash theme={null}
claude mcp add --transport http mangrove \
--scope user \
--header "Authorization: Bearer YOUR_AUTH_TOKEN" \
--header "X-Mangrove-Token: YOUR_MANGROVE_API_KEY" \
https://mcp.gomangrove.com/mcp
```
Replace the placeholders:
* `YOUR_AUTH_TOKEN`: Provided by your Mangrove account team
* `YOUR_MANGROVE_API_KEY`: Your API key from Mangrove API Settings
The `--scope user` flag makes the server available in all your Claude Code projects. Omit it to install only for the current project.
In any Claude Code session, run `/mcp` to confirm the `mangrove` server is connected, then ask:
"What projects do I have access to in Mangrove?"
[Codex CLI](https://developers.openai.com/codex) is OpenAI's terminal agent. It supports remote MCP servers with static Bearer tokens and custom headers through its TOML config.
Add to your shell profile (`~/.zshrc`, `~/.bashrc`, etc.):
```bash theme={null}
export MANGROVE_AUTH_TOKEN="YOUR_AUTH_TOKEN"
export MANGROVE_API_TOKEN="YOUR_MANGROVE_API_KEY"
```
Reload your shell or run `source ~/.zshrc` after editing.
Add the following block to `~/.codex/config.toml` (create the file if it doesn't exist):
```toml theme={null}
[mcp_servers.mangrove]
url = "https://mcp.gomangrove.com/mcp"
bearer_token_env_var = "MANGROVE_AUTH_TOKEN"
env_http_headers = { "X-Mangrove-Token" = "MANGROVE_API_TOKEN" }
```
Codex automatically adds `Authorization: Bearer ${MANGROVE_AUTH_TOKEN}` from the env var.
Start a Codex session and ask:
"What projects do I have access to in Mangrove?"
[Gemini CLI](https://github.com/google-gemini/gemini-cli) is Google's terminal agent. It connects to remote MCP servers over HTTP and sets custom headers itself, so it needs no bridge package.
From any terminal, run:
```bash theme={null}
gemini mcp add --transport http --scope user \
--header "Authorization: Bearer YOUR_AUTH_TOKEN" \
--header "X-Mangrove-Token: YOUR_MANGROVE_API_KEY" \
mangrove https://mcp.gomangrove.com/mcp
```
Replace the placeholders:
* `YOUR_AUTH_TOKEN`: Provided by your Mangrove account team
* `YOUR_MANGROVE_API_KEY`: Your API key from Mangrove API Settings
`--scope user` writes to `~/.gemini/settings.json` and makes the server available everywhere. Leave it off and Gemini CLI writes to `.gemini/settings.json` in your current directory, where the server is available only in that project.
To edit the file yourself instead, add the server to `~/.gemini/settings.json`:
```json theme={null}
{
"mcpServers": {
"mangrove": {
"httpUrl": "https://mcp.gomangrove.com/mcp",
"headers": {
"Authorization": "Bearer $MANGROVE_AUTH_TOKEN",
"X-Mangrove-Token": "$MANGROVE_API_TOKEN"
}
}
}
}
```
Header values accept `$VAR_NAME`, so your credentials can live in environment variables rather than in the file. Set them in your shell profile:
```bash theme={null}
export MANGROVE_AUTH_TOKEN="YOUR_AUTH_TOKEN"
export MANGROVE_API_TOKEN="YOUR_MANGROVE_API_KEY"
```
You can also paste the token values into the `headers` block directly.
Run `gemini mcp list` from your shell, or `/mcp` inside a Gemini CLI session, to confirm the `mangrove` server shows as connected. Then ask:
"What projects do I have access to in Mangrove?"
## Connect multiple Mangrove accounts
If you manage more than one Mangrove account, you can connect all of them to the same assistant and ask about each one by name. Each Mangrove API key belongs to one account, so generate a key in each account's API Settings and add the server once per account. The MCP auth token stays the same for every connection.
Name each connection after its account, so you can tell the assistant which one to use. In Claude Desktop, add one entry per account to `mcpServers`. Only two things differ between entries: the name, and the API key in `env`. Each `env` block belongs to its own entry, so `args` stays exactly the same in both.
```json theme={null}
{
"mcpServers": {
"mangrove-account-a": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://mcp.gomangrove.com/mcp",
"--transport",
"http-only",
"--header",
"Authorization:${AUTH_TOKEN}",
"--header",
"X-Mangrove-Token:${MANGROVE_API_TOKEN}"
],
"env": {
"AUTH_TOKEN": "Bearer YOUR_AUTH_TOKEN",
"MANGROVE_API_TOKEN": "ACCOUNT_A_API_KEY"
}
},
"mangrove-account-b": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://mcp.gomangrove.com/mcp",
"--transport",
"http-only",
"--header",
"Authorization:${AUTH_TOKEN}",
"--header",
"X-Mangrove-Token:${MANGROVE_API_TOKEN}"
],
"env": {
"AUTH_TOKEN": "Bearer YOUR_AUTH_TOKEN",
"MANGROVE_API_TOKEN": "ACCOUNT_B_API_KEY"
}
}
}
}
```
In Claude Code, run `claude mcp add` once per account:
```bash theme={null}
claude mcp add --transport http mangrove-account-a --scope user \
--header "Authorization: Bearer YOUR_AUTH_TOKEN" \
--header "X-Mangrove-Token: ACCOUNT_A_API_KEY" \
https://mcp.gomangrove.com/mcp
claude mcp add --transport http mangrove-account-b --scope user \
--header "Authorization: Bearer YOUR_AUTH_TOKEN" \
--header "X-Mangrove-Token: ACCOUNT_B_API_KEY" \
https://mcp.gomangrove.com/mcp
```
Replace `ACCOUNT_A_API_KEY` and `ACCOUNT_B_API_KEY` with each account's API key. Cursor takes the same `env` blocks as Claude Desktop. In VS Code, Codex CLI and Gemini CLI, give each entry its own input id or environment variable for the API key (for example `MANGROVE_API_TOKEN_A` and `MANGROVE_API_TOKEN_B`); entries that share one read the same key and connect to the same account.
Then name the account in your prompt:
```
"Using mangrove-account-b, list the projects in that account"
```
## Example Prompts
Once connected, you can use natural language to query your Mangrove data. Here are example queries organized by common use cases:
### Production & Operations
```
"Show me all events from project abc123 in the last 30 days that are missing data points"
"What locations are associated with project xyz789 and when was the last event recorded at each?"
```
### Accounting & Compliance
```
"List all batches created in Q4 2024 with their CI scores, grouped by feedstock type"
"Which events from location loc_xyz have missing evidence files that I need for the upcoming audit?"
```
### Analysis & Insights
```
"Compare the average mass flow rates across my three production locations over the past quarter"
"Show me the trend in CI scores for corn feedstock over the last 6 months"
```
### Inventory & Credits
```
"What's the total volume of credits issued, retired, and transferred in 2024 for project abc123?"
"Show me all pending orders and their current status"
```
## Important Considerations
AI-generated insights are produced by language models interpreting your Mangrove data. While powerful for exploration and analysis, always verify critical results against source data. MCP responses should complement, not replace, human judgment for compliance and reporting decisions.
The underlying data returned from Mangrove APIs is accurate and reflects your system of record. However, the AI assistant's interpretation and summary of that data may occasionally contain inaccuracies or miss nuanced details.
# Create a model run
Source: https://docs.mangrovesystems.com/api-reference/model-runs/create
POST /models/{model_id}/model_runs
Create and execute a model run. Provide the time range and the data point IDs to use as inputs. The model's node tree is evaluated against the provided data points.
# Delete a model run
Source: https://docs.mangrovesystems.com/api-reference/model-runs/delete
DELETE /model_runs/{model_run_id}
Delete a model run. The delete is refused with 422 while the run is still processing, while a downstream model run reads its outputs, while it produces a batch, while a score set references it, and while a pending, completed or verified report uses it. Each refusal carries a `code` and names the records in the way, the same entries `deletion_blockers` returns on [Retrieve a model run](/api-reference/model-runs/get).
# Get a model run
Source: https://docs.mangrovesystems.com/api-reference/model-runs/get
GET /model_runs/{model_run_id}
Get a single model run with its calculations and output data points. `is_deletable` says whether [Delete a model run](/api-reference/model-runs/delete) will succeed, and `deletion_blockers` lists what stands in the way when it will not.
# Get all model runs
Source: https://docs.mangrovesystems.com/api-reference/model-runs/get-all
GET /models/{model_id}/model_runs
List all model runs for a specific model.
# Create a model
Source: https://docs.mangrovesystems.com/api-reference/models/create
POST /projects/{project_id}/models
Create a new model in a project. The model is defined via a YAML string that describes the node tree structure.
# Create a new model version
Source: https://docs.mangrovesystems.com/api-reference/models/create-version
POST /models/{model_id}/versions
Create a new version of an existing model with an updated YAML definition. Can only be called on the latest version. The new version inherits the model name and increments the version number.
# Delete a model
Source: https://docs.mangrovesystems.com/api-reference/models/delete
DELETE /models/{model_id}
Delete a model. By default, only the latest version is deleted, and it must have no model runs.
Use the all_versions parameter to delete all versions of the model at once.
# Retrieve a model
Source: https://docs.mangrovesystems.com/api-reference/models/get
GET /models/{model_id}
Returns a specific model by ID.
# List all models
Source: https://docs.mangrovesystems.com/api-reference/models/get-all
GET /models
Returns an array of all active model versions with filtering and sorting capabilities.
# Get model schema
Source: https://docs.mangrovesystems.com/api-reference/models/get-schema
GET /models/{model_id}/schema
Get a model's schema including required inputs, outputs, and available operators. Useful for discovering what inputs a simulation needs.
# Get model YAML
Source: https://docs.mangrovesystems.com/api-reference/models/get-yaml
GET /models/{model_id}/yaml
Generate YAML representation of a model's current node tree.
# Lint model YAML
Source: https://docs.mangrovesystems.com/api-reference/models/lint-yaml
POST /models_yaml_lint
Format and lint YAML content. Reorders keys to follow standard convention and fixes indentation. Does not require a specific model - operates on raw YAML.
# Simulate a model
Source: https://docs.mangrovesystems.com/api-reference/models/simulate
POST /models/{model_id}/simulate
Run a dry-run simulation against a model using provided input values. Does not create a model run or persist results.
# Update a model
Source: https://docs.mangrovesystems.com/api-reference/models/update
PATCH /models/{model_id}
Update model metadata. Does not modify the model's node tree - use create_version for that.
# Update a model version description
Source: https://docs.mangrovesystems.com/api-reference/models/update-description
PATCH /models/{model_id}/description
Sets the description on one model version. Send `null` or an empty string to clear it. The `description` key is required, so a request without it returns `400` and leaves the stored value as it is.
# Update a model version label
Source: https://docs.mangrovesystems.com/api-reference/models/update-version-label
PATCH /models/{model_id}/version_label
Sets the version label on one model version. Send `null` or an empty string to clear it. The `version_label` key is required, so a request without it returns `400` and leaves the stored value as it is.
# Validate model YAML
Source: https://docs.mangrovesystems.com/api-reference/models/validate-yaml
POST /models/{model_id}/yaml_validate
Validate a YAML string against a model's config without persisting. Performs multi-layer validation including YAML syntax, structure, business logic, and simulation readiness.
# Retrieve an order
Source: https://docs.mangrovesystems.com/api-reference/orders/get
GET /orders/{order_id}
Returns a specific customer order by ID.
# List all orders
Source: https://docs.mangrovesystems.com/api-reference/orders/get-all
GET /orders
Returns all orders.
# The Order object
Source: https://docs.mangrovesystems.com/api-reference/orders/order
An Order represents a request made by a customer for credit inventory.
### The Order object
Unique ID of order, generated by Mangrove
Status of the order (e.g., `COMPLETE`, `PENDING`)
Name of the customer placing the order
Order reference number (e.g., `A1234567890`)
Date when the deal was made
Expiry date of the order, or `null` if not set
Currency code for the order (e.g., `USD`)
Payment terms for the order (e.g., `Pay on delivery`)
Method of delivery (e.g., `RETIREMENT`)
Array of delivery objects associated with the order
Timestamp when the order was created
Timestamp when the order was last updated
# Pagination
Source: https://docs.mangrovesystems.com/api-reference/pagination
How collections are paginated in the API.
Endpoints which return collections (e.g. `/projects`, `/orders`) will return paginated results.
The following parameters can be passed for paginated objects:
| Parameter | Meaning |
| - | - |
| page | Page to return. Defaults to `1`. |
| page\_size | Number of items per page. Defaults to `20`, with a maximum of `200`. A value above the maximum is treated as `200`, and a value below `1` as `1`. |
```
{
page: 1,
page_size: 10
}
```
Here's an example query string `/api/v1/projects?page=1&page_size=10`.
If an object is paginated, the results will be returned in the `data` object and pagination information will be returned in the `info` object:
```json json theme={null}
{
"data": [...],
"info": {
"items": 10,
"page_size": 10,
"total_items": 15,
"current_page": 1,
"next_page": 2,
"previous_page": null,
"total_pages": 2
}
}
```
| Field | Meaning |
| - | - |
| items | Number of items returned in the results |
| total\_items | Total number of items across all pages |
| current\_page | Current page of the request |
| next\_page | Page number of next page of results |
| previous\_page | Page number of previous page of results |
| total\_pages | The total number of pages of results |
### Filtering
List endpoints take filters as `filter[field][comparator]=value`, for example `/api/v1/projects?filter[name][contains]=Kiln`. Each endpoint's reference page lists the fields and comparators it accepts.
A filter the endpoint does not accept returns `400` with a message naming the problem: an unknown field, a comparator the field does not support, a `filter` parameter that is not in the `filter[field][comparator]` shape, or a value that cannot be read as the field's type.
```json theme={null}
{
"errors": [
{ "message": "Unsupported comparator 'zz' for filter 'name'. Supported comparators: eq, neq, gt, geq, lt, leq, contains." }
]
}
```
# Retrieve all projects
Source: https://docs.mangrovesystems.com/api-reference/projects/get
GET /projects
Returns an array of all projects with filtering and sorting capabilities.
# Retrieve a project
Source: https://docs.mangrovesystems.com/api-reference/projects/get-project
GET /projects/{project_id}
Returns a specific project by ID.
# Update a project
Source: https://docs.mangrovesystems.com/api-reference/projects/patch
PATCH /projects/{project_id}
Updates a project. Only project owner or account admin can update projects.
# The Project object
Source: https://docs.mangrovesystems.com/api-reference/projects/project
A Project is defined as a set of activities that, as a whole, produces net climate benefits relative to a baseline scenario.
A Project's boundary is unique to the technology pathway it adopts - all stages within the project's boundary contribute to the carbon accounting of the net carbon impact it produces.
Within the Mangrove instance of the Project, data is collected from across the boundary in the form of events and static inputs, and run through a data flow that models the calculations required to generate the net carbon impact of the Project.
### Attributes
Unique ID of project, generated by Mangrove
Name of the project
Description of the project
The specific technology that the carbon project adopts. Examples of options are `Direct Air Capture`, `Biochar` , `Ocean Alkelinity Enhancement`
Array of credit types associated with the project (e.g., `CORC`)
Country of the primary facility on a project
State or province of the primary facility on a project
Status of the project. Options are defined by the Admin of the Mangrove instance.
Value options: `DRAFT` , `PRE-SALES`, `ACTIVE` , `CANCELLED`
Name of the carbon registry associated with the project (e.g., `Puro.Earth`)
Link to the project's registry page
The methodology used for the project (e.g., `Puro Biochar Methodology v3 2023`)
Array of objects representing images of the project. Each object contains `name` and `url` properties
List of UN sustainable development goals associated with the project. A list of available sustainable development goals can be found [here](https://sdgs.un.org/goals).
* `id`
* `name`
* `description`
# Rate Limits
Source: https://docs.mangrovesystems.com/api-reference/rate-limits
Limits on requests to the Mangrove API.
Requests to the Mangrove API are subject to rate limiting based on the access token used. This means that once a token exceeds a predefined number of requests within a given time frame, the API will return a `429 Too Many Requests` response.
Each response from the Mangrove API includes three headers related to rate limiting:
* `RateLimit-Limit`: The maximum number of requests allowed within a 60-second window.
* `RateLimit-Remaining`: The number of requests still available in the current 60-second window.
* `RateLimit-Reset`: A Unix timestamp indicating when the current rate limit window resets. After this timestamp, the request allowance is replenished to the value specified in RateLimit-Limit.
## Default Rate Limits
By default, the Mangrove API enforces a rate limit of **1,000 requests per minute** per access token.
For higher rate limits or custom allocations, please contact Mangrove API support.
# Create an entry
Source: https://docs.mangrovesystems.com/api-reference/report-map-entries/create
POST /reporting/report_map_versions/{version_id}/entries
# Delete an entry
Source: https://docs.mangrovesystems.com/api-reference/report-map-entries/delete
DELETE /reporting/report_map_entries/{id}
# Get an entry
Source: https://docs.mangrovesystems.com/api-reference/report-map-entries/get
GET /reporting/report_map_entries/{id}
Returns entry with inline parameter_bindings.
# List entries
Source: https://docs.mangrovesystems.com/api-reference/report-map-entries/get-all
GET /reporting/report_map_versions/{version_id}/entries
Returns entries with inline parameter_bindings.
# Update an entry
Source: https://docs.mangrovesystems.com/api-reference/report-map-entries/patch
PATCH /reporting/report_map_entries/{id}
# Create a version
Source: https://docs.mangrovesystems.com/api-reference/report-map-versions/create
POST /report_maps/{report_map_id}/versions
Creates a new version seeded with copies of the latest version's entries and parameter bindings.
# Delete a version
Source: https://docs.mangrovesystems.com/api-reference/report-map-versions/delete
DELETE /reporting/report_map_versions/{id}
# Get a version
Source: https://docs.mangrovesystems.com/api-reference/report-map-versions/get
GET /reporting/report_map_versions/{id}
# List versions
Source: https://docs.mangrovesystems.com/api-reference/report-map-versions/get-all
GET /report_maps/{report_map_id}/versions
# Update a version
Source: https://docs.mangrovesystems.com/api-reference/report-map-versions/patch
PATCH /reporting/report_map_versions/{id}
# Create a report map
Source: https://docs.mangrovesystems.com/api-reference/report-maps/create
POST /projects/{project_id}/report_maps
Create a report map. Supports nested versions_attributes for creating the full hierarchy (versions, entries, parameter bindings) in one call.
# Delete a report map
Source: https://docs.mangrovesystems.com/api-reference/report-maps/delete
DELETE /projects/{project_id}/report_maps/{id}
# Get a report map
Source: https://docs.mangrovesystems.com/api-reference/report-maps/get
GET /projects/{project_id}/report_maps/{id}
# List report maps
Source: https://docs.mangrovesystems.com/api-reference/report-maps/get-all
GET /projects/{project_id}/report_maps
Returns paginated report maps for a project. Each report map defines how model nodes map to external registry parameter keys.
# Update a report map
Source: https://docs.mangrovesystems.com/api-reference/report-maps/patch
PATCH /projects/{project_id}/report_maps/{id}
# Create a report
Source: https://docs.mangrovesystems.com/api-reference/reports/create
POST /reports
Creates a new report. Requires project_id and quantification_methodology_id.
# Delete a report
Source: https://docs.mangrovesystems.com/api-reference/reports/delete
DELETE /reports/{report_id}
Deletes a report if it is deletable.
# Retrieve a report
Source: https://docs.mangrovesystems.com/api-reference/reports/get
GET /reports/{report_id}
Returns a specific report by ID.
# List all reports
Source: https://docs.mangrovesystems.com/api-reference/reports/get-all
GET /reports
Returns an array of all reports with filtering and sorting capabilities.
# Invite an auditor
Source: https://docs.mangrovesystems.com/api-reference/reports/invite-auditor
POST /reports/{report_id}/invite
Gives an external auditor read-only access to one report and emails them an invitation naming it. The auditor signs in to Audit Hub to review the report, its batches, its documents, and its evidence.
The call is idempotent. Sending an address that already has access resends the invitation rather than creating a second grant, so it is also how you resend.
Requires Audit Hub to be enabled on the account, and a token with Accounting and Reporting write permission.
# Update a report
Source: https://docs.mangrovesystems.com/api-reference/reports/patch
PATCH /reports/{report_id}
Updates a report. Only draft reports can be updated.
# Revoke an auditor
Source: https://docs.mangrovesystems.com/api-reference/reports/revoke-auditor
DELETE /reports/{report_id}/revoke
Withdraws an auditor's access to one report, addressed by email. The report stops being reachable for them on their next request. Access to any other report shared with the same auditor is untouched.
Requires Audit Hub to be enabled on the account, and a token with Accounting and Reporting write permission.
# Submit report to registry
Source: https://docs.mangrovesystems.com/api-reference/reports/submit
POST /reports/{report_id}/submit
Sends a report to its registry. The report must be in `draft` or `failed` status and meet all submission requirements, so a report whose last submission failed can be sent again.
The send runs in the background and the response carries the `async_task_id` to follow. When the send fails before it starts, the response carries `async_task_id: null` and the failed `registry_submission` instead, with its reason and next step. Every attempt is listed in `registry_submissions` on [Retrieve a report](/api-reference/reports/get).
# Re-evaluate a rule over a period
Source: https://docs.mangrovesystems.com/api-reference/rule-runs/create
POST /projects/{project_id}/rules/{rule_id}/runs
Runs the rule again across a period you name, and returns the run so you can follow it.
You do not need this after writing an event, applying a batch correction or setting a data point value. Each of those already re-evaluates the records it touched. Reach for it when a rule was fixed after the fact, or when data changed underneath a period that has already been evaluated.
Both bounds are required. The dashboard treats a missing bound as unbounded, but a script that omitted one would silently widen the run to the project's whole history, so here an absent bound is refused.
A run that completes without changing a verdict is a normal outcome, not a failure.
# Retrieve a rule run
Source: https://docs.mangrovesystems.com/api-reference/rule-runs/get
GET /projects/{project_id}/rules/{rule_id}/runs/{id}
Returns a single run, including its progress and counts.
# List a rule's runs
Source: https://docs.mangrovesystems.com/api-reference/rule-runs/get-all
GET /projects/{project_id}/rules/{rule_id}/runs
Returns the rule's runs, most recent first.
# Revert a rule run
Source: https://docs.mangrovesystems.com/api-reference/rule-runs/reverse
POST /projects/{project_id}/rules/{rule_id}/runs/{id}/reverse
Takes a run back, reverting the verdicts it wrote. The run transitions immediately, so a second call finds a run that can no longer transition, while the unwinding itself happens in the background.
Corrections the run wrote are removed and each value goes back to the one it replaced, except where the correction sits in a reported batch or somebody has corrected the value since. `reversal_summary` counts them as `corrections_planned` and `corrections_deleted`.
Rows are left untouched where a verdict was dismissed, where the row sits in a batch that has been reported, and where a later run has taken it over. Each is listed in `reversal_summary`.
Check `reversible` before calling. When it is false, `non_reversible_reason` says why.
# Create a static input
Source: https://docs.mangrovesystems.com/api-reference/static-inputs/create
POST /projects/{project_id}/static_inputs
Creates a new static input for a project.
# Delete a static input
Source: https://docs.mangrovesystems.com/api-reference/static-inputs/delete
DELETE /projects/{project_id}/static_inputs/{static_input_id}
Deletes a static input. Cannot delete if it has events in a batch.
# Retrieve a static input
Source: https://docs.mangrovesystems.com/api-reference/static-inputs/get
GET /projects/{project_id}/static_inputs/{static_input_id}
Returns a specific static input by ID.
# List all static inputs
Source: https://docs.mangrovesystems.com/api-reference/static-inputs/get-all
GET /projects/{project_id}/static_inputs
Returns all static inputs for a project.
# Update a static input
Source: https://docs.mangrovesystems.com/api-reference/static-inputs/patch
PATCH /projects/{project_id}/static_inputs/{static_input_id}
Updates a static input. Changing value_type requires providing a new value.
# Status Codes
Source: https://docs.mangrovesystems.com/api-reference/status-codes
Status codes returned by the Mangrove API.
The Mangrove API returns standard HTTP success or error status codes. For errors, the API will also include extra information about what went wrong encoded in the response as JSON.
| Status Code | Meaning |
| - | - |
| 200 | OK |
| 201 | Object Created |
| 400 | Bad Request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not Found |
| 422 | Unprocessable Entity |
| 429 | Too Many Requests |
| 500 | Internal Server Error |
| 503 | Service Unavailable |
Errors are returned as a list, so one response can report several problems at once:
```json json theme={null}
{
"errors": [
{ "message": "Record not found" }
]
}
```
Every entry carries a `message`. Validation failures also carry the `field` the problem is on and a `code`, and some carry extra keys naming the offending values. A `code` is a stable slug on the errors that define one, and repeats the message text on the errors that do not, so branch on a `code` only where you have seen a slug come back. Endpoints that refuse a request without validating a record return a `message` alone:
```json json theme={null}
{
"errors": [
{
"message": "A rule with the same expression already exists: 'Fill ambient temperature dropouts'",
"field": "rule_text",
"code": "duplicate_rule",
"duplicate_rule": {
"friendly_id": "rule_Qb3k9TzdL0pWnXyz",
"name": "Fill ambient temperature dropouts",
"effective_from": "2026-01-01",
"effective_to": null
}
}
],
"duplicate_rule": {
"friendly_id": "rule_Qb3k9TzdL0pWnXyz",
"name": "Fill ambient temperature dropouts",
"effective_from": "2026-01-01",
"effective_to": null
}
}
```
A duplicate rule is the one error that also repeats its detail outside the list, as a `duplicate_rule` sibling of `errors`, so a client can read it without walking the entries. Its `message` ends with the existing rule's effective period in parentheses, which the example above leaves off; read the period from `duplicate_rule` rather than parsing the text.
A `401` from an expired token carries a message saying so. A `401` from a token Mangrove does not recognise carries no body at all, which is how the two are told apart. See [Authentication](/api-reference/authentication).
The split between `404` and `422` is diagnostic. A `404` points at the path or the record lookup, such as a wrong ID or a record in another account. A `403` is the permission answer, where the token is valid and lacks the permission the endpoint needs. A `422` is business validation, where the request was understood and refused. If a call that used to work starts returning `404`, suspect the path or scoping rather than the payload.
# API Support
Source: https://docs.mangrovesystems.com/api-reference/support
Get Support with the Mangrove API
For any issues with the Mangrove API, please contact [api@mangrovesystems.com](mailto:api@mangrovesystems.com) for support.
Each API request has an associated request identifier. You can find this value in the HTTP header, under `X-Request-ID`. To expedite the resolution process for API support, please provide the request identifier when you contact us about a specific request.
# Changelog
Source: https://docs.mangrovesystems.com/changelog
Additions and updates to the Mangrove platform.
#### Data Rules
* **Run a rule only on demand.** Turn on **Run only on demand** for a rule that should run when you decide a period is complete, rather than on every data point as it arrives. Saving it evaluates nothing, and it runs over a period you start with **Evaluate a period**. A substitute rule set this way can read the capture rate of a data point other than the one it corrects. See [Rules that run on demand](/data-rules/create-a-rule#rules-that-run-on-demand).
* **Fill a gap from the whole month.** A calendar window's new **Covers** setting offers **Whole month**, which reads the whole period, including data points after the one being corrected, so a month's gap can be filled from the month's full average. **Whole month** is available on rules that run on demand.
* The method that copies another data point type's value, **Another data point in the same event**, is now called **Cross fill** in the rule builder and in correction history.
* On a proposal waiting for approval, the buttons read **Use proposed** and **Keep current**.
* Rule runs and data feed runs say **Revert** throughout, so **Undo run** reads **Revert run**.
* **Substitution is available on every account.** Substitute rules, corrections and their approval no longer need to be switched on.
* **Fill from another data point type.** Every substitution method except **Constant** can read its values from another data point type, set in **Data point to use**. **Cross fill** can copy a data point type from a different event type, matched to the same asset over the same period.
* **Measure missing data as time.** `TIME_NOT_CAPTURED` returns how much of a period had no data, so a rule can fire when a month is missing more than a set number of hours. Available where data capture rate is enabled. See [Time not captured](/data-rules/rule-expressions#time-not-captured).
#### Production Accounting
* **Edit assets in bulk.** Export the asset list, edit the file, and import it back, with a preview of every change before anything is saved. **Import history** lists past imports, and **Revert import** puts an import's edits back. Enabled per account. See [Editing assets in bulk](/production-accounting/assets#editing-assets-in-bulk).
* A model version takes an optional **Description** alongside its **Version Label**.
#### Data Collection
* Fixing a value that has never been substituted, such as a typo, changes it directly with no correction added to its history. A value that already carries a substitution gets a new correction. To keep the captured value beside the new one, record the change as a substitution. See [Substituting a value](/data-collection/data-points#substituting-a-value).
* Filter the data points table by **Value** for a single Number, Boolean or Select data point type.
* Selecting rows on the data points table opens a selection bar with **Export selected**, **Delete** and **Clear selection**.
#### Reporting
* A failed registry submission shows its reason on the report's **Submissions** card, and the next step when you open it with **View**. A failed report can be sent again. See [When a registry submission fails](/reporting/integrations#when-a-registry-submission-fails).
#### API
* **Every filter is applied or refused.** An unknown filter field, a comparator the field does not support, or a malformed `filter` parameter returns `400` naming the problem. A filter value that cannot be read returns `400` rather than `422`. Batch lists no longer take `filter[state]`, event data points take `filter[value]` only once narrowed to one data point type, and methodologies no longer take `filter[report_type]`. See [Filtering](/api-reference/pagination#filtering).
* Project data points take `filter[slug][eq]` and `filter[start_time]` and `filter[end_time]` with `geq` or `leq`, alongside the plain `slug`, `start_time` and `end_time` parameters. Sending a time bound both ways, or two different slugs, returns 400.
* `page_size` is capped at `200`, and defaults to `20`.
* Rules carry `evaluation`, `automatic` or `on_demand`, and a calendar window takes `extent`, `to_date` or `whole_period`. `backfill_enqueued` on a rule update says whether a re-evaluation started, so it reads `false` on an on-demand rule.
* A model run carries `is_deletable` and `deletion_blockers`, and a refused delete returns each blocker with a `code` and the records in the way.
* **Breaking:** a batch's `input_data` rows carry a fixed set of fields, which adds the data point's `substituted`, `imputed`, `raw_value` and `substitution` and the event's `project_id`. Evidence, rule results and the full event record are no longer on the row; read them from their own endpoints, using the row's `event.project_id` and `event.id`.
* `ui_url` on ledgers, models and batches uses friendly IDs, and a report's `ui_url` opens it inside its project.
* Project and event data points take `filter[value][eq]` once the list is narrowed to one Number, Boolean or Select data point type, and sort by `start_time` or `end_time`. Data points and events have a fixed default order, `start_time` then `id`.
* A list or object sent as an event's value returns `400`, and a number outside the supported range returns `422`.
* `summary_with_export` is refused with `400` on data point types, project locations, batch evidence and report maps.
* A model carries `version_label` and `description`, each with its own `PATCH` endpoint.
* A model run carries `processed` and `incomplete_nodes`, naming the nodes an incomplete run could not compute.
* A batch or model run used by a pending, completed or verified report refuses deletion with the `frozen_reports` blocker.
* A report carries its `registry_submissions`, and submitting a failed report starts a new submission.
#### Data Rules
* **Catch a sensor stuck on one value.** A new `WITHIN TRAILING` window reaches back from the data point being evaluated and never forward, so a rule can test that the lowest and highest values over the last hour are the same. A windowed rule can now act on the whole window, or on the whole run of repeats, rather than only on the most recent data point: turn on **Flag the whole run** or **Correct the whole run** on the Then step. See [Catching a stuck sensor](/data-rules/rule-expressions#catching-a-stuck-sensor).
* **Spot an outlier against its own history.** `ZSCORE` returns how many standard deviations a value sits from the average of its window, so a rule can flag a value that is inside its permitted range but out of character for that stream. See [Spotting an outlier](/data-rules/rule-expressions#spotting-an-outlier-with-zscore).
* **Hold corrections for approval.** A substitute rule can propose each correction instead of writing it. The data point keeps its measured value until someone approves or rejects the proposal in the Event Detail drawer, and each decision is recorded with who made it. A data point with a proposal waiting cannot go into a new batch. Approve and reject over the API too. See [Approve corrections](/data-rules/approve-corrections).
* **Substitute with a percentile.** A new **Percentile** method replaces a value with a chosen percentile, such as the 90th or the median, of the valid values in a window.
* **Calendar windows in the rule builder.** A substitution method's window can be set to a calendar period, with a number of periods back and a span of several consecutive periods, directly in the rule wizard. Last month's average, or the previous two complete years, no longer needs the API.
* Test a computed value with a Batch Calculation rule: calculated data point types no longer appear in a Data Points rule's autocomplete, and a condition naming one is rejected when you save.
* **Compare two data points with `RATIO`.** A rule can divide one measurement by another and test the result, so two meters that should agree, or a week's average against the year's, becomes one condition. Each side is a data point or an aggregate over one, and the two sides can carry different windows. See [Catching drift between two data points with RATIO](/data-rules/rule-expressions#catching-drift-between-two-data-points-with-ratio).
* The rules list carries a **Rule ID** column, switched on from **Columns**, and the search box matches a rule's name or its ID.
* **Undo run puts corrected values back.** Undoing a substitute rule's run restores each value it corrected, except a value in a reported batch or one somebody has corrected since.
* A substitute rule that has written corrections can now be deleted. The corrected values stay, and each keeps the rule's name and ID in its history.
* Alert cards name the rule that raised them, with its ID and effective period.
* A value covering a span of time counts toward a window only if it overlaps it, so a calendar bucket no longer takes in a data point that ended exactly as the bucket began. Corrections drawn from such windows can change as a result. See [What to know before you use one](/data-rules/rule-expressions#what-to-know-before-you-use-one).
* **Duplicate Project** copies the project's data rules, each with a new rule ID and no run history.
#### Data Collection
* **Needs attention** replaces the Health Check button on the Events table. It lists the values waiting on a person, with a count against each queue: **Alerts**, and, where substitutions are enabled, **Data points to review**. Pick a queue to filter the table to it.
* **Restore to original** now puts a corrected value back to the original in one step, however many corrections stand on it. It previously undid only the most recent correction.
* The capture rate's **Substituted** count carries its own share of what was expected, and breaks down by what wrote each correction, with **Show matching data points** on each line to filter the Events table to that set. Substitution detail needs a date range of 92 days or less. See [Data capture rate](/data-collection/data-capture-rate).
* **Reverse** on a bulk job is refused once its data points have fed a model run, so a reversal cannot pull values out from under a calculated result. The job names the reason.
* A data feed run breaks down what it created by event type and data point type, with counts of alerts and substitutions that open the matching values. See [Bulk import](/data-collection/bulk-import).
* Where substitutions are enabled, the correction history in **Event Detail** reads as one trace from the value recorded to the value in force, including proposals and the decisions on them.
* Where substitutions are enabled, the Analytics chart marks the periods that hold substituted values.
* The Events export carries an **Alert Reason** column, and, where substitutions are enabled, **Original Value**, **Substitution Rule ID** and **Substitution Reason**. Its **Origin** column reads `Reverted` for a restored value. See [Exporting](/data-collection/data-points#exporting).
* Charts no longer count a batch or data point that ends exactly on a period boundary toward the next period as well.
#### API
* **Reverting a data point value restores the original value**, clearing every correction on it, rather than stepping back one correction. The endpoint keeps its shape and status codes.
* Rules carry `window_member_scope` and `requires_approval`. Rule results carry `approval_state`, `proposed_correction` and the `rejected_*` fields, and two new operations [approve](/api-reference/data-rules/approve) and [reject](/api-reference/data-rules/reject) a proposed correction.
* A substitution method's window accepts `kind: trailing` as a spelling of `rolling`, a calendar window takes a `value` counting consecutive periods, and `percentile` joins the method list. The [grammar endpoint](/api-reference/data-rules/syntax) serves the method catalog alongside the expression grammar.
* Creating a ledger or a report accepts the project's friendly ID (`prj_...`) as well as its numeric ID. An unknown project now returns "Project not found".
* **Updating a data point or an event records a value change as a correction**, so the value as captured stays available as `raw_value` beside the current value, and the change appears in the correction history. Submitting the value already held records nothing. Clearing a value with null or a blank string is rejected.
* An asset custom field label that matches a standard column of the Assets spreadsheet is rejected with a 422, since the export would otherwise write two columns under one heading.
* [Undo all of a rule's corrections](/api-reference/data-rules/revert-corrections) puts back every value a rule corrected, and reversing a rule run removes the corrections that run wrote. Deleting a rule is no longer refused because it has written corrections.
* [Bulk approve](/api-reference/data-rules/bulk-approve) and [bulk reject](/api-reference/data-rules/bulk-reject) decide up to 100 proposed corrections in one call. A rule's results filter by `approval_state`, and rules and rule stats carry `pending_count`.
* Projects, event types, ledgers, batches, models, reports and rules carry `ui_url`, a link that opens the resource in the app.
* Sending `cadence_amount` or `cadence_unit` on an account without the data capture rate feature returns 422 instead of being ignored.
* `GET /feature_flags` returns every flag that affects the API, with `false` for the ones that are off.
#### Production Accounting
* The Generate Score drawer lists the operational inputs the selected batches supply, with each one's **Value** and **Source Batch**. See [Generate scores](/production-accounting/generate-scores).
#### Reporting
* **Audit Hub** gives the auditor reviewing your report their own way into Mangrove. Invite them by email from the report's **Auditors** card, and they get read-only access to that report's batches, documents, and evidence, with everything previewable in place and downloadable as one package. Remove access at any time, and re-invite the same auditor later. Enabled per account; talk to your Mangrove account team to switch it on. See [Share a report with an auditor](/reporting/audit-hub).
* Reports assemble a **package** of their own: a calculation workbook per batch, the source workbooks behind each model, a CSV per event type, and the evidence and documents attached to the report. It rebuilds when you add something the report includes.
* Batch workbooks now open on the quantification model sheet and carry the source-data correction columns, so a reviewer can see the value as captured, what replaced it, and which value the calculation used.
* Batch workbooks carry a **Substitutions Summary** tab, with counts and totals split by whether a value was substituted, and a link through to each source sheet. Source rows now carry a start and an end time instead of a single timestamp, each naming its timezone.
#### Data Rules
* A rule's action can now be **Substitute** rather than Alert, so it corrects the values it matches. Add substitution methods in the rule wizard and Mangrove works down them in order until one produces a value: a fixed constant, another data point on the same event, the nearest earlier or later data point, or the average, lowest or highest across a trailing window. Where nothing produces one, the rule raises an alert instead. Enabled per account.
* Every rule keeps a **run history** on a **Runs** tab. Re-run a date range with **Evaluate a period**, and take back what a run wrote with **Undo run**.
* Filter the rules list by rule type.
* Substitute rules can also be written over the API. See [Correcting many values at once](/api-reference/cookbooks/substitute-values).
* The rule **preview evaluates windowed aggregates**, so a `WITHIN` condition previews the result it will produce once saved. It previously read them against an empty set. Preview also applies the same validation as saving.
* A substitution window takes an **offset**, which steps it back whole periods so a method can draw on the previous month rather than this one so far.
#### Data Collection
* **Declare how often an event type is expected to report**, and Mangrove tracks how much of that data actually arrived. The capture rate sits on the Data Points toolbar, breaks a shortfall into substituted and missing, and a rule can alert on it with `DATA_CAPTURE_RATE`. Leave a stream on **Irregular** where it has no schedule. Enabled per account. See [Data capture rate](/data-collection/data-capture-rate).
* Filter the Data Points table by **Value is missing** to find gaps directly, with no data point type filter needed first.
* The **Origin** column filter is simpler: a gap that has since been filled now sorts under **Substituted** with every other changed value, rather than needing its own option. Exports still label the two separately.
#### Account Admin
* **Sign in with Microsoft Entra ID.** The sign-in page now asks for an email address first and sends a connected domain on to Microsoft. Configured by your Mangrove account team. See [Single sign-on](/account-admin/single-sign-on).
#### API
* **Breaking: deleting a batch returns `202` and a task**, not `204` and an empty body. Poll the returned task at `GET /async_tasks/{task_id}`, which is now documented. Deletion blockers are still checked before anything is queued.
* **Breaking: event type endpoints no longer accept a numeric ID.** Use the friendly ID, for example `et_AbCd1234567890Ef`.
* **Filter events by event type friendly ID, event type slug, or location.** `filter[event_type_id]`, `filter[slug]` and `filter[location_id]` are new and accept only the `eq` comparator. `filter[nexus_event_type_id]` still takes the numeric ID for callers that already have it. An unrecognised filter key is dropped rather than rejected, so a misspelled filter returns unfiltered results with a `200`.
* **Rule runs over the API.** List a rule's runs, re-evaluate a period, read a run back, and reverse one. See [Rule Runs](/api-reference/rule-runs/get-all).
* Event types carry `cadence_amount` and `cadence_unit`.
* Assets accept a flat `filter[project_id]=prj_...` as well as the bracketed form.
* Report map endpoints accept `prj_` friendly IDs.
* The batch CSV export on a ledger is now scoped to that ledger. It previously exported every batch on the account.
* **API tokens now expire.** New tokens default to 90 days, with 30, 60, 180 days, 1 year, and no expiration also available. Expiry cannot be changed after creation, and the **Expires** column flags a token in its last 14 days. Tokens created before this change keep working. See [Authentication](/api-reference/authentication).
#### Data Collection
* The **Events** table now lists your data one data point at a time, so each measured value carries its own status, alerts, and provenance. A data point recorded without a value shows as **Missing**. Available on every account.
* Filter and sort at the value level: quick filters for event type and data point type, numeric filtering and sorting on a single data point type, and **Filter by AI** for plain-language requests like "anomalous data points from the last month".
* Export the Events table to **CSV** or **Excel**, flat and one row per value, honouring whatever filters you have applied.
* Deleting data points now shows what it will affect before you commit. Choose whether to remove the whole event or only the selected values, see the counts for each choice, and have locked values skipped rather than silently dropped.
#### Data Rules
* **Substitute a data point's value** without editing the original record. Replace a wrong value or fill a gap where no value exists, and the original stays readable with the method, source, and author recorded against the change. The most recent substitution can be restored with **Restore to original**, and the whole history stays on the data point. Substitutions are enabled per account; talk to your Mangrove account team to switch them on.
* Substitutions are written through the [API](/api-reference/corrections/set-value) or from a data feed transformation. Filter the Events table by **Substituted** to find every changed value, or by **Value was missing** to isolate the filled-in ones.
* Alerts now land on the data point they are about. A rule that reads several values can name which of them can be flagged, so triage goes to the value that actually breached.
* Bulk alert dismissal now accepts at most **1000** results per request. See [Dismissing many rule alerts at once](/api-reference/cookbooks/bulk-dismiss-alerts) for how to split a larger triage.
#### Production Accounting
* Two new batch partition strategies combine dimensions: **Tracking ID + Feedstock** and **Tracking ID + Asset**.
* Batch generation allocation is more predictable. An amount drawn from a source batch is the total for the generation, divided evenly across the partitions that run, so allocating one source batch across several partitions no longer draws it once per partition.
* Generating batches on a ledger no longer generates its concurrent ledgers as well. Pass `generate_concurrent_ledgers` to opt back in. See [Generating batches](/api-reference/cookbooks/batch-generation).
#### Data Rules
* **Windowed aggregates** let a rule ask questions across events. Use `WITHIN ROLLING` for a sliding window ("a cluster of data points within seven days either side") or `WITHIN CALENDAR` for a reporting period ("more than one delivery in a calendar month"). See [Rule Expressions](/data-rules/rule-expressions).
#### Production Accounting
* Track the physical equipment behind your production data as **Assets**: chargers, meters, wells, kilns, vehicles. Manage them in each project's **Settings → Assets** page, with per-asset coordinates and an exportable asset list.
* Attach events directly to the asset that produced them. Event types can require an asset so per-equipment data never loses its linkage during ingestion.
* Partition batches by asset: one batch per piece of equipment in the generation window, a natural fit for programs that report per-unit.
#### Admin
* **Custom Fields are now available on Assets.** Define typed per-equipment attributes (text, number, boolean, date, select, URL) in **Account Admin → Custom Fields → Assets** (serial numbers, registration statuses, registry-assigned IDs) and set values per asset.
#### Data Rules
* Configure project **Data Rules** in **Project Settings → Data Rules** to automatically validate event data at ingestion or model outputs after batch calculation. Write expressions that flag out-of-range measurements, missing values, and methodology violations.
* Scope each rule's evaluation window with **Effective from / Effective to** dates: rules auto-evaluate every matching record in range, with no separate backfill step.
* Triage **rule alerts** directly in event drawers, batch detail pages, and reports. Dismiss alerts with a reason to preserve audit history.
#### Production Accounting
* Maintain multiple versions of a model with drafts and active versions. Edit a model and save it as a labeled draft without affecting live calculations, or set it as the new active version on save to pin it for batch generation. Override the version at batch generation time to preview how a draft will calculate before activating it.
#### Data Collection
* Connect a SQL database with your credentials to pull data daily into Mangrove and transform it into accounting-ready events. Mangrove initially supports MSFT SQL Server, Snowflake (Direct and Secure Share.)
* Auto-generate transformations on data feed creation using an example import CSV file. Edit and test transformations in the editor before you run bulk data ingestion.
#### Production Accounting
* Generate Carbon Intensity scores using your project's ongoing production accounting. Use different score models (e.g. CA-GREET, ISCC GHG Add-on) to simultaneously generate various scores using the same production data.
* Mangrove calculations backed by industry-standard Excel workbooks (e.g. CA-GREET, OR-GREET, GHGenius) can be generated, with the populated workbooks saved down as proof for auditability.
#### Data Collection
* Set up new Data Feeds directly in the Input Settings to receive and transform bulk data.
* Using the POST data feeds endpoint, send bulk CSV files from your various frontline systems directly to your Mangrove Project through the Mangrove API. See API documentation for latest.
* Filter for the set of events that was generated from a bulk import or bulk file in the project's Bulk Jobs table.
* Evidence files can now be associated with individual datapoints within your project's Event data. Attach different evidence for different datapoints (e.g. weigh tickets for transport mass vs route maps for transport distance).
* Preview XLS/XLSX evidence files to compare the raw ingested data to event data synced in Mangrove. Use Mangrove's AI scanning to scan Excel evidence files and auto-generate event data.
#### Production Accounting
* Use your favorite AI tool to connect to your Mangrove data through the Mangrove MCP Server. Ask questions about your project, collected data, production batches and reports and get answers quickly.
#### Admin
* Manage Custom Fields for projects. Admins can create Custom Fields and define their type, visibility on project overview pages and order.
#### Projects
* Add Custom Fields to a project and enter their values.
#### Data Collection
* See audit trails of past edits to event data, for visibility into how the data has changed over time.
#### Production Accounting
* Auto-generate route maps with distances as evidence for transport emissions calculations.
* The Mangrove API has been updated with many new endpoints. Retrieve feedstocks, ledgers, production batches, model calculations, reports and make changes directly through the API. See API documentation for latest.
#### Production Accounting
* Evidence files can be uploaded when generating batches which allocate mass from one ledger to another. Manage evidence files from allocated batches from the calculations table.
#### Projects
* Project details are now shown in the overview. Use the edit link or navigate to Settings to update project details.
* Manage your project information and configuration in Settings.
* Add a registry and edit registry settings in the Registry section of Settings.
#### Production Accounting
* On the Project Overview, current and cumulative ledger balances are now shown for each ledger.
#### Inventory Management
* On the Project Overview, a credit inventory summary added with an interactive chart for credits issued and delivered.
#### Admin
* Assign the user role "Member - Read Only" to new or existing users. Users with this role can view core features, but cannot add, edit or delete data.
#### Production Accounting
* Filter production accounting batches by multiple paramaters, such as output values, net carbon removal, and locations. Use Filter By AI to filter for batches simply by describing the filter criteria.
#### Reporting
* Include the associated location names and latlongs on reported batches in Mangrove's auto-generated statements.
* Regenerate any auto-generated statements to capture updates to the batch calculations within a draft Report.
#### Data Collection
* Filter the feed of operational data collected in Mangrove by multiple paramaters, such as locations of events, evidence, tracking ID, and data source.
* Use Filter By AI to filter the events feed simply by describing the filter criteria. Let AI help filter, sort, and show simple aggregate calculations.
#### Production Accounting
* Track inventories of renewable instruments (RECs, book and claim units) within a project, and allocate them to be used in production accounting.
#### Reporting
* See which parts of calculations in Mangrove are reported to a registry through API, through a registry label alongside the calculations.
* Registry submissions through API generates an audit log for easy review of the submission activity.
* Mangrove's updated integration with Isometric's Certify API enables projects to package up their MRV data and report removals to the Isometric registry.
#### Production Accounting
* Track a list of embodied emissions within the project, and set corresponding amortization rules for each embodied emission.
* Generate batches easily by reviewing inputs required by category, syncing existing project data, and adding new data and evidence.
* Skip inputs that aren't relevant to a specific batch - Mangrove automatically assumes a 0 value for calculations.
#### Data Collection
* Mangrove has overhauled delivery tracking. Collect data during every truck delivery with an improved tracking experience. Drivers scan a QR code per delivery, and record information at every step of the way (tank fill, start delivery, end delivery, end use), along with photos or files as evidence.
#### Reporting
* Auto-generate monitoring reports and GHG statements from reported accounting data, with embedded lists of evidence and verification views. Package and submit a Report, and track submission statuses.
* Auto-update a project's forecasted credit inventory based on submitted reporting, generating an updated estimate of credit quantity and issuance time. Replace existing credit forecasts for the same production period.
#### Admin
* Manage a portfolio of project owners, each with access to their own Mangrove Managed Accounts where they can submit data, review production accounting, and reporting for each of their projects. Summarize the credit inventory and customer orders across all your managed partners' projects, all in a central Portfolio account.
#### Data Ingestion
* Track an audit log of changes on a Data Request to see when and where submissions were made by your data partners.
* Add a new data source to a project using example spreadsheet data, for ongoing bulk imports. Attach transformations to the import data to normalize the import into events.
#### Production Accounting
* Directly add and build new acccounting models within each project.
* Organize your production accounting summary into various project stages.
* Partially allocate production batches to downstream production ledgers.
* Download an Excel version of your projects' quantification models, and the calculations backing up a production batch, for flexible validation and analysis.
#### Credit Inventory
* Select all projects, or all projects of a particular methodology, to see the credit inventory summary.
#### Data Ingestion
* Switch between map style layers to view location data in the context of terrain or satellite imagery.
#### Production Accounting
* Admins can run a test on every node calculated in a model to ensure accuracy of models. See errors and missing inputs in context of the model itself to understand where to adjust the calculations.
#### Reporting
* Users with Admin privileges can initiate a new Report from production data. View all Reports across various statuses within the Reporting section of the project. Assign Reports to specific Account Admins to own the submission and completion.
* Sync the Report directly as a GHG Statement to the Isometric registry.
#### Credit Inventory
* The vintage year of a credit issuance is auto-assigned as the end date of the production period. Users can now update the vintage year for issued spot credits if the actual vintage differs from this auto-assigned year.
#### Data Ingestion
* Directly create delivery events with origin and destination in Mangrove, and auto-calculate road transport driving distances for emissions accounting.
* Attach URLs as evidence to events and static inputs, to support data with web-available documentation or deep links to large-format evidence such as 3D models hosted in other systems.
#### Production Accounting
* Track mass flows and balances across many stages of the project, including emissions and carbon intensity calculations of individual units of material. Get a quick a summary overview across balances.
* View an aggregated list of associated evidences within the detail view of each batch of production.
#### Data Ingestion
* Send data requests to custom email recipients to request one-off data submissions from external parties. CC yourself to ensure they've received the requests.
* Upload new files to your project and auto-detect data from the file to be saved as operational events.
* Preview a range of evidence file types and CSV files within the Mangrove Platform, to cross-check the accuracy of the collected data against the attached files.
#### Production Accounting
* Account for the ongoing generation of multiple co-products and environmental attributes/credits from a single project, across parallel accounting ledgers.
#### Credit Inventory
* Transfer and retire Puro CORCs directly from within Mangrove to your customers' registry accounts, synced to the Puro registry.
* View credit inventory schedule by issuance period, alongside cumulative inventory availability and upcoming deliveries.
#### Reporting
* Directly report MRV data to CSI using the EBC-Sink and Global C-Sink Standards
#### Data Ingestion
* Mangrove integrates data directly from your SQL databases like Snowflake. Provision access credentials to your Snowflake database for continuous data fetching, or use Snowflake's Secure Data Sharing to share data with Mangrove.
* Track all metadata of unique deliveries with Mangrove-generated Tracking Units - embed QR codes into delivery workflows and auto-calculate emissions for transport legs of the journey.
* Categorize the data being collected from your partners by naming the Data Request
* Cancel, retry, or reverse your Bulk Imports, to ensure you're bringing the right project data into Mangrove.
#### Production Accounting
* Analyze detailed gross-to-net carbon accounting on every batch of production
#### Credit Inventory
* Mangrove integrates directly with your Salesforce account, allowing credit inventory and deliveries in Mangrove to be synced with your contacts and opportunities in Salesforce.
#### Data Ingestion
* Mangrove integrates data directly from Endress + Hauser's sensor hardware, including through E+H's Netilion SCADA.
* Integrate with the Mangrove API to push project data, events, and evidence directly into your project in Mangrove.
#### Data Analysis
* Run a data Health Check on your feed of operational events, and filter for incomplete or anomalous events.
* View an updated home dashboard, including a map of all project production, and a feed of latest notifications that need your attention.
#### Production Accounting
* View the chain of custody for every batch, with locations of key operational activity marked on a map.
* Drill into the source events and evidence for calculations within each production batch.
#### Reporting
* Generate PDF reports alongside spreadsheet statements for registry reporting
* Skip manually generating PDF and Excel statements, and push reporting directly from your Mangrove account to Carbon Standards International for fast verification.
#### Credit Inventory
* Within the Customer Portal, credit buyers can easily review past and upcoming credit deliveries, and see location information within the MRV calculations of each delivery
#### Data Ingestion
* Send a test Data Request to yourself, before sending it to data partners
* Auto-calculate driving distance and routes based on start and end latlongs of ingested transport trips for transport emission quantification, using Mapbox's Directions API within your transformations
#### Reporting
* Generate Puro-compliant CORC Output reports with data from a reporting period of your project.
#### Data Ingestion
* Data partners can add notes to the files that they submit to provide more context
* See a map of locations and asset hierarchy of each project in the Project Boundary section.
* In the Project Boundary section, add and manage locations and their child assets to build a full location graph of each project.
#### Data Analysis
* View a dashboard of key ingested time-series data for every project - see trends, catch outliers, view gaps in the data.
* Filter for specific time ranges on each dashboard chart, and zoom into the specific event data on the feed.
#### Production Accounting
* Admin users can test calculations in project models to be sure that calculations are properly modeled.
* Simulate a run of the model by entering a series of inputs and viewing the calculated results.
#### Credit Inventory
* Export all credit deliveries in a project, and across projects, into a CSV or Excel file for easy analysis.
* Certain registries such as Puro charge a flat per unit fee per credit. Besides %-based fees, users can now set a registry flat fee per project, and adjust it per credit delivery, to accurately capture the revenue breakdown of each order.
#### Admin
* Scale your Mangrove MRV implementation as you scale to new deployed projects.
* Duplicate an existing project's configuration to set up a new project.
* Copy over the project type, methodology, models, and data sources all at one go.
#### Data Ingestion
* View and edit how files imported through specific Data Sources are transformed into events using a Python-based transformations editor.
#### Production Accounting
* Write equations as complex expressions in the model builder.
* Analyze production trends over time and the breakdown of allocatable production batches with a user-friendly Production Accounting dashboard.
* Filter production batches to view and manage the accounting within a particular production period.
#### Reporting
* Skip manually generating PDF and Excel statements, and push reporting directly from your Mangrove account to Isometric for fast verification.
#### Data Ingestion
* Request data and documentation from your data partners for individual data inputs, and review submissions.
* Include detailed descriptions and links to help your partners understand what to submit.
* Transform data into rich events with a set of associated datapoints.
#### Production Accounting
* Admin users can edit calculations within your project's production models using the built-in script-based model editor.
* Add and edit nodes, check the model, and save it to update calculations.
* View all the project's models in one place in the Project's "Models" section, including production and quantification models under the project's accounting data flow.
* See details, inputs, and settings for models.
#### Credit Inventory
* Preview delivery details as credit deliveries are created, including estimated revenue.
* Fulfill a delivery by manually allocating credits from specific issuances on a project.
* Save documents such as contracts and agreements with each customer order to centralize all information about the credit order in one place.
#### Data Ingestion
* Send email requests or share a request link to collect data and documentation from your data partners, and track the progress of submissions.
#### Customer Management
* Add your customers to a branded Customer Portal where they can view the credits that have been delivered to them, as well as upcoming scheduled deliveries.
* For each credit delivery, customers can see in-depth MRV calculations, evidence documentation, as well as general information about the project and methodology.
#### Production Accounting
* Before running a dataflow to perform production accounting, users can validate the completeness of data inputs from a reporting period in real-time.
#### Data Ingestion
* Bulk import production data exported from various systems within your project environment into Mangrove in various file formats, including XLS, XLSX, and CSV files.
#### Credit Inventory
* The Orders section has been enhanced. Users can search for orders by order ID, and review the delivery schedule on each Order with deeper levels of detail on each delivery, including delivery dates and payment statuses.
#### Customer Management
* Allow your credit buyers to integrate APIs from Mangrove to pull information about their orders, upcoming credit deliveries, and underlying MRV calculations for delivered credits.
#### Production Accounting
* Production accounting within each project can be done through a data flow that is structures calculations sequentially across different production models.
* Calculated data points that are generated from models are saved onto the project's events feed as a growing stream of production data.
* In the Value Streams section, see a list of value streams that are active across your projects.
* View the quantification model of each value stream, as well as the static inputs used for net carbon quantification.
#### Credit Inventory
* Browse your inventory across all projects in one consolidated schedule view - add multiple projects to compare your credits on hand.
* See all issuances within the same Inventory section.
* Add recurring forecasts (monthly, quarterly, or annually) to the credit inventory of each project to project out your expected inventory.
#### Data Ingestion
* Ingest string and boolean-type inputs, to represent descriptive data or yes/no events
* See the configured bulk import data sources within each project's Input Settings.
* Bulk import events using the default event schema, or work with Mangrove to configure new data sources per project.
* Track the status of past and ongoing bulk imports.
#### Production Accounting
* Ingest event data with tracking IDs. Generate one batch per tracking ID, to track the accounting and chain of custody data under each physical SKU.
* Filter and search the events feed - by input type, date range, and event status.
* See events with evidences attached.
#### Credit Inventory
* Issuances that are `Pending Issuance` represent credits that are no longer just Active Forecasts but are in the process of being issued.
* When production data is allocated to a forecast, the issuance automatically switches to `Pending Issuance`.
* The user will need to Issue the credits to be able to complete deliveries.
* The Customers section has been updated with improved search and filtering.
* Better search and filtering within the Orders and Customers sections.
* Use quick filters to easily narrow the list of relevant Orders to Open Orders or Fully Delivered.
#### Admin
* Configure account brand settings for external interfaces such as portals and verification views.
* Set brand colors and logos.
# Activate a SQL data connector
Source: https://docs.mangrovesystems.com/data-collection/activate-data-connector
Connect external SQL databases to automatically sync data into your Mangrove project.
## Connector Overview
Mangrove offers pre-built data connectors for common database systems. Once activated, connectors automatically sync data from your external systems into your project.
* The connectors authenticate and fetch data from the database on a scheduled cadence. This is set to a daily schedule of 11:59pm ET; we plan on making the cadence configurable soon.
* Data is fetched from a set cursor value - this allows only the latest data to be pulled into Mangrove
* SQL queries are run to fetch the data in CSV format
### Available Connectors
* Microsoft SQL Server
* Snowflake
* Snowflake (Secure Share)
Don't see your database? Contact the Mangrove team
to discuss additional connector options.
## Activate a Connector
**Before you begin**
You will need:
* Database connection details (host, port, database name)
* Database credentials (username and password)
* The event type(s) you want to transform the database data into in Mangrove
### Adding a new SQL-fetch Data Feed
In your project, go to **Data Inputs** > **Input Settings**. Click **Add Data Feed** and select a database connector.
Select your database type.
Enter your database connection details (host, port, database name, username, password), then click **Test Connection** to verify access.
Mangrove connects directly to your database. Check that your firewall allows connections from Mangrove's IPs. Contact support for the current allowlist.
Add the SQL query, and test it to preview the results. You can also add a pre-query to retrieve the right tables for selecting data.
Ensure you have set:
* the cursor field
* an initial cursor value
### Writing SQL Queries
When writing your SQL query, you can reference the following variables. These variables enable dynamic queries based on the database configuration and the SQL pre-query.
| Variable | Description | Example Value |
| - | - | - |
| `{{cursor_field}}` | The name of the column used as a cursor to track sync progress | `last_updated` |
| `{{cursor_value}}` | The most recent value fetched for the cursor field (e.g. timestamp or incrementing id) | `2024-05-31T23:59:00` |
| *Pre-query variables* | Any columns returned from your configured pre-query, referenced by name | |
You can reference these variables in your main SQL query using double curly braces, for example:
```sql theme={null}
SELECT * FROM {{my_table_from_prequery}}
WHERE {{cursor_field}} > '{{cursor_value}}'
AND company_id = '{{company_id_from_prequery}}'
ORDER BY {{cursor_field}} ASC
```
## Troubleshooting
- Verify the credentials are correct
- Verify that there isn't any firewall or VPN necessary to access the database.
- Verify the source table contains data
- Check that field mappings are correctly configured
- Ensure the database user has SELECT permissions on the mapped tables
- Check if multiple connectors are pointing to the same table
- Verify the sync schedule isn't running too frequently
## Managing Connectors
You can update the credentials for the connector in the **Input Settings** > **Data Feed**, then click **Edit** beside the SQL source to modify connection details.
# Add events directly
Source: https://docs.mangrovesystems.com/data-collection/add-data
Directly add individual events in the Data Inputs section of the project.
Notes on specific parameters of each event:
* **Event Type:** Select one of the configured input types for the project
* **Date Range:** Start and end date of the event
* **Tracking ID (optional):** This field can be used to specify an external reference ID for the event, which the production models can use when processing the data. For example, the invoice/weighbridge number of a delivery can be captured as the event's Tracking ID.
* **Locations:** Choose the origin and destination for the trip from the project's locations, or add a new location from either dropdown. Mangrove maps the route between the two and, with **Update distance and attach route map as evidence** ticked, fills in the event's distance data point and attaches a route map as evidence. This field appears on event types with **Requires locations** enabled; see [Track transport events](/data-collection/add-transport-events).
* **Add Evidence (optional):** You can optionally add multiple files or URLs to an event to provide evidence for the events tracked.
## Adding Evidence
Evidence serves as supporting documentation to back up the data collected and reported to auditors. Auditors can review this evidence to determine the accuracy of your reporting and validate the claims made in your events.
#### Evidence Types
Evidence can be provided in two formats:
* **Files:** Upload physical documents, images, videos, or other file types directly to the event
* **URLs:** Provide links to external resources or documents hosted elsewhere
#### Supported File Types
The platform supports a wide range of file formats for evidence uploads:
| Category | Supported Formats |
| - | - |
| Documents | PDF, Excel (.xls, .xlsx), Word (.doc, .docx), Text files (.txt), CSV files |
| Images | JPG/JPEG, PNG, GIF |
| Videos | MP4, MOV (QuickTime), AVI |
| Archives | ZIP files |
| Geospatial | GeoJSON, Shapefiles |
| Code | JSON |
When adding evidence, ensure that your files clearly support the data and claims made in your event. High-quality, relevant evidence will help streamline the verification process and build trust with auditors.
If your project has [Evidence Checks](/data-collection/evidence-checks) enabled, uploaded evidence will be automatically checked against your datapoint values using AI. You'll see validation results directly on each datapoint.
# Track transport events
Source: https://docs.mangrovesystems.com/data-collection/add-transport-events
Record transport trips within your project for transport emissions calculations.
Mangrove makes it easy to directly record transport events such as incoming and outgoing material deliveries, or waste disposal trips, and account for the emissions from trip through a distance-based or fuel-based calculation methodology. Here's how to do it:
Add new transport events directly on the Mangrove platform.
Generate QR-code based tracking units to be used as part of frontline delivery workflows.
## Direct entry in Mangrove
1. User can **Add Event** to directly record a specific type of transport trip, either while you **Generate** a batch, or separately in the **Data Inputs** of a project.
2. Simply choose the relevant project locations as the origin and destination
3. Leave **Update distance and attach route map as evidence** ticked to have Mangrove work out the trip distance for you. It fills in the event's distance data point in that field's own unit (for example kilometers or miles), and you're free to adjust the value manually. Where more than one route is possible, Mangrove claims the longest one, which reports transport distance, and so transport emissions, conservatively.
4. After you save the transport event, Mangrove attaches a route map image to the distance data point. It shows the claimed route alongside the alternatives, the distance, and when it was worked out, and serves as evidence for auditors reviewing the event later on.
## Tracking through a QR Tracker
This feature is separately configured by the Mangrove team on your account. Please speak to us about activating.
Data from transport trips can be collected through Mangrove's QR-code based tracker - allowing frontline staff to log important information at various stages of the trip.
#### Generating QR Tracking Units
1. A Mangrove user can generate any number of Tracking Units in **Data Inputs > Tracking Units**. Each Tracking Unit has:
* an immutable ID
* an associated QR code
* a unique web URL
2. Tracking Units can be downloaded in bulk as PDFs
* Each PDF includes the unique QR code as well as project-specific instructions
* These instructions are configured in the **Tracking Units > Add Instructions** dropdown
3. Mangrove users can view the status of each tracking unit
* Only tracking units that are not already in use can be activated for new transport trips or deliveries.
Need options for printing/attaching tracking units? Please discuss the optimal way to operationalize the QR tracking with the Mangrove team.
#### Collecting data with Tracking Units
1. The Mangrove user shares the generated tracking units to the frontline staff who are making the deliveries and collecting information
2. For each unit of delivery, frontline staff scans the QR code and records the relevant information at various stages of the delivery.
# Bulk import data
Source: https://docs.mangrovesystems.com/data-collection/bulk-import
In the Data Inputs section, bulk import data through a project-specific Data Source.
## Importing data
Prepare the raw data file that you need to bring into your project in Mangrove. Acceptable file formats include CSV, XLS, and XLSX.
* Ensure you're using the right import template
* Populate all required columns in the template
To streamline imports, ensure that data in the import templates reflect the correct formatting (e.g., date format or datetime format) and units (e.g., values in (%) should be between 0 to 100). A best practice is to "paste as value only" into the import templates to avoid copying external links and formulae into the template.
Upload the file into the corresponding Data Source in the Mangrove platform
* Orders recorded for this customer will have the currency set as the default
* Save contacts, and important information about the customer's registry accounts
Events are transformed from your bulk import and populate the Analytics charts and Events Feed.
* View event data: by selecting each event on the feed
* Attach evidence files to relevant events: with **Upload Evidence** on each event
* Filtering events: You can filter for specific event types and time ranges in the feed and on the Analytics charts
To check the status of ongoing bulk imports, open **Data Inputs > Data Feed Runs**. Click a run to see what it created, broken down by event type and data point type, with a count of **Alerts** and **Substitutions**. Each count opens the Events table filtered to those alerts or substitutions. If you encounter an error that you need assistance with, please reach out to Mangrove through your shared channel.
* Reviewing issues with imports: Issues with bulk imports would be reflected as `errored` runs. Review the error message for more detail on the specific issue transforming the data in your bulk import file into Mangrove.
* Revert an import: a completed run can be reverted with **Revert**, which deletes the events it published. Reverting a run whose data points have already been used in a model run is refused, and the run names the reason.
* **Retry** an import
## Data transformations
Transformations can be written by Admin users to be applied to every new file bulk imported from that data source.
Transformations can be applied to bulk import files to generate events and evidences from them.
See [Transformation contract](#transformation-contract) for the variables a script can read and the event object shape it must produce.
### Transformation contract
A transformation is a Python script that runs once per import file. The execution environment exposes three top-level variables:
| Variable | Type | Description |
| - | - | - |
| `rows` | `list[dict]` | One dictionary per row in the import file. Access columns by header name, e.g. `row["Date"]`. |
| `filename` | `str` | The name of the uploaded file, including extension (e.g. `"march-shipments.csv"`). |
| `results` | `list[dict]` | The script must declare this (`results = []`) and append one event object per event it wants to create. |
A minimal script:
```python theme={null}
from datetime import datetime
results = []
for row in rows:
results.append({
"event_type": "shipment",
"start_time": datetime.strptime(row["Date"], "%m/%d/%Y").isoformat() + " " + row["Timezone"],
"end_time": datetime.strptime(row["Date"], "%m/%d/%Y").isoformat() + " " + row["Timezone"],
"data_points": [
{"slug": "shipment-mass", "value": row["Shipment Mass"]},
],
})
```
#### Event object shape
Each object appended to `results` represents one event. The fields below mirror what Mangrove's transformation runtime accepts.
**Required**
| Field | Type | Description |
| - | - | - |
| `event_type` | `string` | Slug of the event type to create. Must match a configured event type on the project. |
| `start_time` | `string` | Timestamp in `%Y-%m-%d %H:%M:%S%z` format (e.g. `2024-02-01 12:00:00-04:00`). |
| `end_time` | `string` | Timestamp in `%Y-%m-%d %H:%M:%S%z` format. |
| `data_points` | `object[]` | Each entry has a `slug` (matching a configured data point) and a `value` matching that data point's value type. Optionally include `evidence_refs: string[]` to link to entries in `evidences` by their `ref_id`. |
**Optional**
| Field | Type | Description |
| - | - | - |
| `tracking_id` | `string` | External identifier for deduplication and reconciliation. Required when the event type is configured to require one. |
| `notes` | `string` | Free-text notes attached to the event. |
| `locations` | `object[]` | Each entry has a `name` (string, required) and may include `lat` and `long` (float). Mangrove matches on `name`; new locations are created if no match exists. |
| `feedstock` | `object` | An existing project feedstock identified by `name` and `feedstock_type`. Required when the event type is configured to require one. |
| `evidences` | `object[]` | Text-based evidence files to attach to the event. Each entry takes one of these shapes:
• `{ "url_reference": "...", "display_name": "..." }`
• `{ "url": "...", "name": "..." }`
• `{ "base64": "...", "name": "...", "type": "..." }`
• `{ "name": "...", "type": "...", "content": "..." }`
Add a `ref_id` to link the evidence to specific data points via their `evidence_refs`. |
### Editing transformations on each Data Source
Admin users can define the event types that are generated from every Data Source through the Transformations Editor.
* In **Data Inputs > Input Settings**, select an existing Data Source
* **Add transformation**
* Select an event type for data from the Data Source
* Edit the Python transformation in the editor
Transformations are written in Python, and need to return a `results` array of objects representing the events to generate from the import data. An example is shown below:
```python Transformation theme={null}
from datetime import datetime
results = []
for row in rows:
event_object = {
"event_type": "",
"notes": row[""],
"start_time": parse_datetime(row[""] row[""]).isoformat(),
"end_time": parse_datetime(row[""] row[""]).isoformat(),
"locations": [
{
"name": row[""],
"lat": 43.6499286,
"long": -79.3858228
}
],
"feedstock": {
"name": "",
"feedstock_type": "Animal Waste"
},
"evidences": [
{
"name": "",
"ref_id": "evidence_1",
"type": "json",
"content": {
"some_key": "some_content"
}
}
],
"data_points": [
{
"slug": "",
"value": row[""],
"evidence_refs": ["evidence_1"]
},
{
"slug": "",
"value": row[""]
}
]
}
results.append(event_object)
```
Here's an example of what the `results` array might look like following execution of the transformation above:
```json results theme={null}
[
{
'event_type': '',
'notes': 'This is an example event note',
'start_time': '2024-02-01 12:00:00-04:00',
'end_time': '2024-02-01 15:30:00-04:00',
'locations': [
{
"name": "",
"lat": 43.6499286,
"long": -79.3858228
}
],
"feedstock": {
"name": "",
"feedstock_type": "Animal Waste"
},
'evidences': [
{
'name': '',
'ref_id': 'evidence_1',
'type': 'json',
'content': {
'some_key': 'some_content'
}
}
],
'data_points': [
{
'slug': '',
'value': '',
'evidence_refs': ['evidence_1']
},
{
'slug': '',
'value': ''
}
]
}
{
# another event object
...
}
]
```
Mangrove will process this result to create events in the `Data Inputs > Events` feed that can be used to run production models.
#### Using external packages in transformations
Apart from the Python standard library, Mangrove also supports a curated list of external packages that you can import and use in transformations:
* `mapbox`
* `boto3`
* `geopy`
* `pandas`
* `numpy`
# Data capture rate
Source: https://docs.mangrovesystems.com/data-collection/data-capture-rate
Measure data completeness: find missing data and gaps where a sensor or feed stopped reporting, before a reporting deadline.
Data capture rate is the share of the data you expected that actually arrived. You declare how often an event type should report, and Mangrove compares that expectation against the measurements it holds: expect 720 hourly measurements in a month, receive 690, and your capture rate is 95.8%.
A data rule checks the values you have. This finds the ones that never came.
```mermaid theme={null}
pie showData
title Expected hourly values in a month
"Measured" : 690
"Substituted" : 18
"Missing" : 12
```
The whole circle is what the cadence expected. Only the measured slice counts toward the rate.
Data capture rate is switched on per account. Ask your Mangrove account team to switch it on if the **Cadence** field described here is not in your project yet.
## What a low rate means
A capture rate below what you expected is almost always one of three things:
| Cause | How to tell | What to do |
| - | - | - |
| A feed or sensor stopped delivering | The **Missing** count is high and clustered in time | Check the data feed, then backfill the period |
| The cadence is tighter than the data really arrives | **Missing** is spread evenly across the whole range | Correct the cadence on the event type |
| Gaps were filled by substitution | The **Substituted** count is high and **Missing** is low | Nothing is missing; the data is estimated rather than measured, and a substituted value never counts as captured |
**Missing** is the line to chase. To see exactly which values are absent rather than how many, filter the **Value** column with the **Is missing** operator on the Events table. See [Review data points](/data-collection/data-points).
Mangrove does not block a report on a low capture rate, and the figure is not part of the package an auditor downloads. What the auditor does see is the substitution summary in each batch workbook, so a gap you filled is visible to them even though the rate itself is not. See [Share a report with an auditor](/reporting/audit-hub).
## Declare a cadence
Cadence sits on the event type, so every data point type under it shares one expectation.
1. Open **Data Inputs**, then **Settings**.
2. Add an event type, or edit an existing one.
3. Expand **show settings** and set **Cadence**.
Pick a preset, from every minute up to every month, or choose **Custom** and give an amount and a unit. Leave it on **Irregular** for anything that arrives when it arrives, such as shipments or lab results. Irregular data has no schedule to measure against, so Mangrove never reports it as short.
`1 day` and `24 hour` are different declarations. Days, weeks, months and years are calendar periods in your account's timezone; hours, minutes and seconds are elapsed time. From `month` upward a value counts toward the period it falls in, so a monthly meter figure filed on the 28th still counts for that month.
## Where to find the figure
The capture rate sits on the **Events** table toolbar under Data Inputs, beside the **Event / data point type** quick filter. Narrow to one data point type and it shows that type's percentage. With several in view it shows a count, and opens into a list of every data point type in scope, worst first.
It always measures against a date range. With a data point type selected and no range of your own, it covers the year to date.
A rate just under 100 shows as "under 100%" rather than rounding up, so a data point type that is one value short never reads as complete.
Three states appear instead of a percentage:
| It shows | Meaning | What to do |
| - | - | - |
| **Not measurable** | A cadence is declared, but nothing was expected in this range, because the event type has never reported or the range predates its first data. | Widen the date range, or check the event type is receiving data. |
| **Not computed yet** | The cadence is set and the figure has not been worked out yet. | Wait for the overnight run, or use **Recompute**. |
| Greyed out | Nothing to measure: no date range, no data point type selected, or the selected type's event type has no cadence. | Set a date range, or pick a data point type whose event type has a cadence. |
Open the figure on a single data point type to break the shortfall down: how many values were expected, how many were captured, and what the rest were. **Substituted** counts values a correction supplied, and carries its own share of what was expected. **Missing** counts values that never arrived.
The substituted count breaks down by what wrote each correction. Each line names the rule or the data feed behind it, with **Rule**, **API**, **Manual** or **Data feed** beneath, and selecting it shows the matching data points by filtering the Events table to that set. The breakdown needs a date range of 92 days or less; over that the substituted share still shows, so narrow the range to see what is behind it.
## What counts as captured
Only a measurement counts toward the capture rate. A substituted value is an estimate, so it does not count, but it is reported separately rather than folded into the gap.
The expectation starts at the first event recorded for that event type, and the current period is measured one interval behind so a value still in transit is not counted as missing. Figures update as data changes and are rebuilt overnight.
## Alert on a period that came in short
Once an event type declares a cadence, a data rule can act on the capture rate with `DATA_CAPTURE_RATE`, so a period that arrived short of what the cadence expected raises an alert. The rule is evaluated when data arrives, so it reports on a period once the feed delivers into it. A feed that is quiet right now shows up in the view above. See [Create a data rule](/data-rules/create-a-rule).
```
DATA_CAPTURE_RATE({{data-point.ambient-temperature-c}} WITHIN CALENDAR MONTH) < 0.8
```
`DATA_CAPTURE_RATE` returns a fraction between 0 and 1, so compare against `0.8` rather than `80`.
Where the limit is written as time rather than a share, `TIME_NOT_CAPTURED` returns the same gap as a span, compared with a duration:
```
TIME_NOT_CAPTURED({{data-point.ambient-temperature-c}} WITHIN CALENDAR MONTH) > 10.days
```
It needs a cadence counted in seconds, minutes, hours, days or weeks. See [Rule Expressions](/data-rules/rule-expressions#data-capture-rate) for the windows both accept and the conditions they reject.
## Cadence over the API
A cadence is readable and writable on the event type endpoints as `cadence_amount` and `cadence_unit`. Both are null when no cadence is declared.
* [Retrieve an event type](/api-reference/event-types/get)
* [Update an event type](/api-reference/event-types/patch)
An event type carries both fields or neither. An update merges with what is stored, so sending one field changes half of a cadence that already exists, while sending one on its own when no cadence is set returns `422`. To clear a cadence, send both as `null`.
On an account without the feature, `cadence_amount` and `cadence_unit` are dropped from the request instead of rejected. The update returns `200` and the cadence is unchanged, so check the response rather than assuming the write landed.
# Review data points
Source: https://docs.mangrovesystems.com/data-collection/data-points
Work through collected data one value at a time in the Events table: filter, sort, find values waiting on attention or review, export, and delete.
The **Events** table in a project's Data Inputs section lists your collected data one **data point** at a time. Each row is a single measured value with its own type, status, and alerts.
Because each value gets its own row, a data point that arrives from a data feed or the API with no value still appears in the table, showing as *Missing* with a status of *Incomplete*. A data point that was never submitted has no row at all, and clearing a value on an existing event removes its row.
## Reading the table
| Column | Shows |
| - | - |
| **Origin** | Whether the value is original, substituted, filled in where it was missing, or reverted. |
| **Time Range** | The event's start and end. |
| **Event Type** | The event type that carried the value. |
| **Data Point Type** | Which measurement this row is. |
| **Value** | The value in effect, with its unit. A data point recorded without a value shows as *Missing*. Where a value has been substituted, this column shows the value now in effect; the value it replaced is in the hover card described below. |
| **Status** | Health of the value, including any rule or evidence alerts raised against it. |
| **Source** | Where the value came from, such as a data feed or manual entry. |
| **Tracking ID** | The event's external reference, when one was captured. |
| **Feedstock** | The associated feedstock, when the event has one. |
| **Asset** | The asset the event was attached to, linked through to that asset. |
| **Created by** | Who recorded the value. |
**Source**, **Tracking ID**, **Feedstock**, **Asset** and **Created by** are hidden until you turn them on. Use **Columns** to show or hide them. Your choice is remembered per project in the browser you set it in.
You can also filter by event ID without showing it as a column.
Substituted values all carry the same marker. Hover one to see how it was derived: the original value, the method, and either the rule that wrote it or the source it came from.
A rule can revisit a correction it already made. The correction history in the **Event Detail** drawer then shows the value as **re-derived** by that rule, with the cause on the line beneath: a data point from an earlier method in the rule's chain became available, the data point it used changed or was deleted, or the available data points changed.
On large projects the row count reads **of over N** rather than an exact total. Mangrove stops counting once it can tell the result set is large, which keeps the table responsive. Narrow the filters to get an exact count.
## Filtering and sorting
Start with the **Event / data point type** quick filter at the top to narrow to the event type or data point type you care about. Add column filters for anything more specific, and combine them freely.
Filtering the **Origin** column offers three options. **Substituted** covers every value that currently holds a correction, including gaps that were empty and have since been filled. **Reverted** finds values that have been restored to their original, and **Original** finds the ones never touched. A value sits under exactly one of the three.
To find the gaps that are still empty, filter the **Value** column with **Is missing**. Unlike the other Value comparisons, it needs no data point type filter first, so it works across the whole table. Filtering **Status** to *Incomplete* also returns them, along with values marked incomplete for other reasons.
To see how much of the data you expected has arrived at all, rather than which individual values are missing, use the capture rate figure on the toolbar. See [Data capture rate](/data-collection/data-capture-rate).
**Filter by AI** takes a plain-language request instead of a filter stack. Ask for "anomalous data points from the last month", "data points pending review", or "data points from this month" and it builds the filter for you.
Filtering by **Value** works once the table is narrowed to a single data point type. Narrow it either way:
* Pick one data point type in the **Event / data point type** quick filter.
* Open **Filters**, add a filter on the **Data Point Type** column, set its operator to **is** or **is any of**, and pick one type.
The Value comparisons then follow that type: number comparisons for a Number type, **is** with true or false for a Boolean type, and **Matches any of** for a Select type. Sorting by **Value** needs a single Number type. The Value filter input and the Value column sort both tell you what to add while they are switched off. If you add other filters alongside it, keep the filter panel on **And**.
## Finding data points that need attention
**Needs attention** on the toolbar lists the values in the current view that are waiting on someone, with a count against each queue. Pick a queue and the table narrows to it; pick the same queue again to clear the filter. It runs no new checks of its own.
| Queue | Holds |
| - | - |
| **Alerts** | Values whose status is *Anomalous* or *Incomplete*, and values carrying a rule alert. On accounts with evidence checks enabled, values carrying an evidence alert as well. |
| **Data points to review** | Values with a correction proposed by a rule and waiting for a decision. See [Approve corrections](/data-rules/approve-corrections). |
| **See everything** | Both queues together. A value can sit in both at once, so this is not their sum. |
When nothing in the current view qualifies, the button reads **All clear**. A count above 99 shows as **99+**.
Values carrying an alert show it in the **Status** column. Alerts are raised per data point, and by default a rule that reads several values can flag any of them. A rule can be set to flag only the values it is really about, which keeps triage on the value that breached. See [Managing alerts](/data-rules/manage-alerts) for how to triage them, and [Create a Data Rule](/data-rules/create-a-rule) for choosing which value an alert lands on.
## Exporting
Click **Export** and pick **CSV (.csv)** or **Excel (.xlsx)**. The export is flat, one row per data point, and it covers everything matching the filters you have applied, so you can scope the file by narrowing the table first.
To export only some rows, select them. The page actions give way to a selection bar showing how many data points are selected, with **Export selected**, **Delete** and **Clear selection**. A selection stays in place while you page and sort, and any change to the filters, dates or data point types clears it.
An Excel export is capped at 100,000 rows and a CSV at 1,000,000. Over the Excel cap the export fails and tells you the row count, so switch to CSV or narrow the filters. Over the CSV cap it reports a generic failure, so if a very large CSV fails, narrow the filters and try again.
In the export, **Status** reads `Alert` when a rule or evidence alert is firing on the value, and **Alert Reason** gives each rule's name and alert message. On accounts with evidence checks enabled, it includes failed evidence checks as well.
The export carries four more columns after **Unit** that describe each substituted value:
| Column | Holds |
| - | - |
| **Origin** | `Substituted`, `Substituted (value was missing)`, `Reverted` or `Original`, matching the **Origin** column in the app. The longer substituted label marks values that were empty before they were filled in. |
| **Original Value** | For a corrected value, the value as captured, or `Missing` when it was a gap that was filled. |
| **Substitution Rule ID** | The rule that wrote the latest correction. Blank when it came from an edit, the API or a transformation, or from a rule that has since been deleted. |
| **Substitution Reason** | The method and source of the latest correction, followed by its note when it has one. |
Exports are prepared in the background. Stay on the page until the file is ready, because the download link is held by the page and is lost if you navigate away.
## Deleting data points
Select rows and click **Delete** in the selection bar. Because data points live inside events, the dialog asks which you meant to remove and previews the effect before anything is deleted:
| Choice | Effect |
| - | - |
| **Delete the whole event and its data points** | Removes the events behind the selected values, along with every other value they carry. |
| **Delete only the selected data points** | Removes just the selected values and keeps their parent events. |
The dialog counts the events and data points each choice would affect, and calls out cases where the choice is made for you. An event cannot exist without data points, so deleting the last value in an event removes the event as well. When both choices would delete exactly the same rows, the choice disappears. Values already used by a model run are locked, and they are skipped and reported as skipped.
A selection larger than 200 data points is deleted in passes of 200. The dialog tells you how many rows this pass covers; re-select the remainder afterwards to continue.
## Substituting a value
A substitution replaces a data point's value without touching the original record, which stays visible with its full lineage.
A substitution is written from one of four places:
* **Editing a substituted value in the app**, which adds your edit to its history as a manual override.
* A **substitute rule**, which corrects every data point matching its condition and keeps correcting new ones. See [Data Rules Fundamentals](/data-rules/fundamentals#what-a-rule-does).
* The **API**, one value at a time or in a batch. See [Correct a data point value](/api-reference/corrections/set-value).
* A **data feed transformation**, applied as the value arrives. See [Bulk import](/data-collection/bulk-import).
Hovering a substituted value tells you which of them wrote it. Every later change to a substituted value appends to the same history, so the data point keeps what it was captured as alongside what replaced it. Editing a row to the value it already shows changes nothing and appends nothing. A rule, the API or a transformation filling a gap records a substitution like any other, and the value sorts under **Substituted** with the rest. Typing a value into an empty cell yourself records an initial measurement and starts no history.
Editing a value that has never been substituted, in the app or with [Update an event](/api-reference/events/patch), changes it in place and starts no history. To keep the captured value on record alongside the new one, write the change with [Correct a data point value](/api-reference/corrections/set-value) instead.
To revert a substitution, click the row to open the **Event Detail** drawer, click the data point's name to expand its correction history, and click **Restore to original**. That puts back the original value, clearing every correction standing on it in one step, and the row's **Origin** becomes *Reverted*. The history keeps each correction and records the restore as one entry from the value in force back to the original. A restore cannot itself be restored, though a later substitution on the same value can be. The rule that wrote the correction does not apply it again.
A correction that a rule has proposed but nobody has decided yet shows **Correction pending approval** in the **Status** column, and the value stays as measured until someone approves it. See [Approve corrections](/data-rules/approve-corrections).
Substituting a value does not recalculate anything downstream. A batch that was already generated keeps the value it was generated with. Data rules do re-evaluate against the substituted value, so a substitution can clear an alert or raise a new one.
# Connect data sources
Source: https://docs.mangrovesystems.com/data-collection/data-sources
Data can be ingested from connected data sources on a project.
To get the most value out of your Mangrove Systems MRV workspace, it's critical to connect the right data sources. Bringing operational, environmental, and activity data into Mangrove enables powerful insights, automated reporting, and reliable traceability. Projects can bring data into their MRV workspace in a few different ways:
### 1. Send data via the Mangrove API
If your project already collects data in your own databases or systems, you can implement a service to automate pushing this data directly to Mangrove using our API (documented [here](/api-reference/events/event)). The Mangrove API is designed to ingest a wide range of data types - timeseries data, geospatial data, unstructured files - that are used for accounting and reporting.
An API token with the right permissions is generated by Account Admins. Ensure that your API token is scoped with *Read* and *Write* permissions for *Data Collection*. Read more on authentication [here](/api-reference/authentication).
This is a great option if you want full control over transforming the data and the integration logic and cadence. Mangrove can provide example scripts in Python to help get started.
### 2. Activate a standard integration within Mangrove
Mangrove offers a growing set of standard out-of-the-box integrations for common data systems such as CRMs and databases. These integrations are the fastest way to begin syncing data from supported providers.
Follow our step-by-step guide to connect your database to Mangrove.
### 3. New Data Sources
For more specialized data sources (such as frontline systems, bespoke field collection workflows, or data collected by third-party operators), Mangrove can work with you to fetch that data on a scheduled basis into your account. We recommend reaching out to the Mangrove team early in your planning phase for such new systems.
# Evidence Checks
Source: https://docs.mangrovesystems.com/data-collection/evidence-checks
Use evidence checks to validate that event datapoints match their attached evidence before reporting.
**Beta feature.** Ask your implementation engineer for access.
## How it works
In **Data Inputs > Input Settings**, open the event type you want to configure. Each datapoint can have Evidence Check turned on or off individually (off by default).
Datapoints with Evidence Check enabled show an icon in the Add Events form.
After an event is created, Evidence Checks runs in the background on all enabled datapoints. Checks also re-run when:
* Evidence is added or removed from a datapoint
* A datapoint value is edited
Each check produces one of two results:
| Result | Icon | Meaning |
| - | - | - |
| **Validated** | ✓ (green) | The datapoint value matches the attached evidence |
| **Alert** | ⚠ (orange) | The datapoint value does not match the attached evidence or no evidence exists |
Click on an alert icon to open the alert detail panel, which shows the entered value alongside the value extracted from the evidence.
From here you can:
* **Dismiss** the alert with a reason (not relevant, false positive, or other)
* **Update the datapoint** to match the value found in the evidence
* **Update the evidence** to re-run the Evidence Check
## Where results appear
Evidence Check results are visible across the platform:
* **Events table**: status icon column (same column as anomaly detection)
* **Event details**: on individual datapoints
* **Batch tables**: on the inputs tab
## Improving future checks with hints
When you dismiss an alert, you can optionally provide a **hint** to improve future extractions. For example, you might specify which table, row, or section in the evidence document contains the correct value.
These hints are stored per datapoint type and incorporated into future checks, making checks progressively more accurate over time. Successful matches are also automatically recorded to reinforce correct field mappings.
## Supported evidence formats
Evidence Checks can extract and validate data from:
| Format | Details |
| - | - |
| **PDF** | Via OCR |
| **Images** | JPG, PNG, TIFF, BMP, HEIF |
| **Spreadsheets** | Excel (.xlsx), CSV |
| **Word documents** | .docx |
Evidence that is provided as a URL or in an unsupported file format will not be checked.
# Data Collection Pathways
Source: https://docs.mangrovesystems.com/data-collection/pathways
Add new events directly on the Mangrove platform.
In the Data Inputs section, bulk import data through a project-specific Data Source.
Connect external databases and systems to automatically sync data into your Mangrove project.
Mangrove can be used to request data and documentation from your data partners on a project through email or by sharing a request link.
Send data to the Mangrove API from your system. See our API Reference for full details.
# Request data from external partners
Source: https://docs.mangrovesystems.com/data-collection/request-data
Send a request to a data partner asking them to upload files and data for a project, then review what comes back.
Data requests let you ask a partner (a feedstock supplier, transporter, lab, or anyone outside Mangrove) to send the data you need for a project. They get a link (or an email), upload files against the specific events you asked for, and you review their submission before it lands in the project.
Need the same data every week or month? See [Schedule recurring data requests](/data-collection/schedule-data-requests) instead of sending a new one-off each cycle.
## Send a data request
Create a request from **Data Inputs > Request Data**.
Choose partner users in **To** (and optionally **CC**). Recipients are notified by email when the request is sent. The email is sent from your name (e.g. *From: John at Southeast Capture*).
Choose the period the partner should report on (e.g. April 1 – April 30). The partner only needs to provide data covering this window.
Under **Events requested**, pick the events you want covered: a fuel delivery, lab analysis, meter reading, etc. The partner sees these as a checklist of what to provide.
Include a short message that appears at the top of the request page.
On send, recipients get an email with a direct link. You can also copy the request URL and share it however you'd like: Slack, text, customer portal. The request shows up immediately on any apps that integrate the Mangrove API.
## Track submissions
The Request Data tab lists every outstanding request with an **N/M events** indicator: the number of requested events the partner has completed.
Open a submission by selecting the Files icon or the request URL. The submission opens in a new tab. You can edit it directly: upload or remove files on the partner's behalf, toggle which events each file covers, and add notes for your records.
## What partners see
Partners don't need a Mangrove login. They open the link and see:
* The date range you're asking about
* A checklist of events to upload files for
* Your message, if any
* An upload area
See [Submit to a data request](/data-collection/submit-to-data-request) for the partner-side experience.
# Schedule recurring data requests
Source: https://docs.mangrovesystems.com/data-collection/schedule-data-requests
Set up a data request to send automatically on a recurring cadence, weekly or monthly, instead of creating a new request each cycle.
If you collect the same data from a partner on a regular cadence (monthly meter readings, weekly fuel deliveries), schedule a recurring data request instead of building and sending a new one each cycle.
A schedule pairs a **template** (the inputs, recipients, and message you want repeated) with a **cadence**. At each interval Mangrove generates a new data request from the template and sends it to your recipients automatically.
## Create a scheduled request
Open the request form from **Data Inputs > Request Data** the same way you would for a one-off. At the bottom of the form, find **Delivery schedule**: a toggle with three options:
* **Send now**: send immediately (one-off)
* **Schedule once**: send a single request at a specific date and time
* **Recurring**: send on a recurring cadence
Pick **Recurring** to reveal the schedule fields.
Set the date and time of the first send. Subsequent sends are calculated from this point.
Choose **weeks** or **months** and an interval from 1 to 52, for example *Every 1 weeks* (weekly) or *Every 2 months* (every other month).
Each generated request covers the window from the scheduled send forward by one cadence. For example, a weekly schedule firing Monday at 9 AM produces a request asking for **Monday 9 AM - next Monday 9 AM**.
Pick when the schedule should stop:
* **Never**: runs indefinitely until you pause or end it
* **On** *\[date]*: stops after the specified date
* **After** *\[N]* **occurrences**: stops after a fixed number of sends
The schedule appears in the Data Requests list with a **Recurring** tag (blue). At each scheduled time, Mangrove spawns a new data request from the template and sends it to your recipients automatically.
## Track scheduled sends
In the Data Requests list, expand a scheduled request to see the sends that have already gone out. Each spawned request appears as a child row with its own `N/M events` status, just like a one-off.
Open the detail drawer for the scheduled request to see the **schedule status card**, which shows:
* The current status: **Active**, **Paused**, or **Completed**
* The cadence and end condition
* **Next send**: when the next request will go out
* **Last send**: when the most recent request was sent
Each spawned send is a normal data request that you and your partners can submit to and review like any other. See [Request data from external partners](/data-collection/request-data) for the submission and review flow.
## Pause, resume, or end a schedule
From the schedule status card in the detail drawer:
* **Pause** an active schedule. Paused schedules stop generating new requests but keep their settings. The list-view tag changes to **Paused** (orange).
* **Resume** a paused schedule to pick back up at the next interval.
Once a schedule reaches its end condition (date passed or occurrence count hit), its status becomes **Completed** (green tag) and it stops sending. Create a new schedule to start again.
Edits to the template (recipients, message, requested inputs) apply only to *future* sends. Already-sent requests stay as they were when the partner received them.
## Limits and behaviors to know
* **One active schedule per data request.** A request can have at most one active or paused schedule at a time.
* **You can't send a scheduled template manually.** While a schedule is active, the template itself can't be sent ad-hoc. Pause or end the schedule first if you need to send a one-off in between.
* **Custom cron isn't supported.** Cadences are limited to weekly and monthly with an integer interval (1–52).
* **Failed sends skip; they don't retry.** If a send fails, the error is logged and the schedule advances to the next window. To backfill a missed period, send a one-off [data request](/data-collection/request-data) covering that window.
# Submit to Data Requests
Source: https://docs.mangrovesystems.com/data-collection/submit-to-data-request
Submit data that is requested for a project in Mangrove.
You may receive a request for data for a project in Mangrove, either through email, or from within the Mangrove platform.
The request directs you a page where you can upload and submit files in response to each requested inputs. Upload any number of files, and select the inputs included in the uploaded files.
# Approve Corrections
Source: https://docs.mangrovesystems.com/data-rules/approve-corrections
Hold a substitute rule's data corrections for approval, review each proposed correction, approve or reject it, and see what a waiting proposal blocks.
A proposed correction is a data correction a substitute rule has worked out but not written: the data point keeps its measured value until a person approves it. A substitute rule normally writes its corrections as soon as a method produces a value. Turn on [**Hold for approval**](/data-rules/create-a-rule#holding-corrections-for-approval) on the rule's Then step and it proposes them instead, and someone on your team approves or rejects each proposal. A rule that corrects many data points raises as many proposals. This page covers finding the proposals, deciding them, and what a waiting proposal blocks in the meantime.
## Where proposed corrections appear
The same proposals show in five places:
| Location | What you see |
| - | - |
| **Events table** | **Needs attention** on the toolbar carries a **Data points to review** queue with a count. Pick it and the table narrows to the values with a proposal waiting, each marked **Correction pending approval** in the **Status** column. |
| **Rules list** | A rule with proposals waiting carries a **waiting** count. |
| **Rule detail page** | A **Waiting on approval** card shows how many proposals are waiting, how old the oldest is, and a **Review** button that opens them. |
| **Event Detail drawer** | The data point shows **Correction proposed**, the value the rule would write, and the method that produced it. |
| **Batch generation form** | Inputs with a proposal waiting are listed on a **Needs approval** tab. |
## Approve or reject a proposed correction
From the Events table or the rule's **Review** button, click the row to open the **Event Detail** drawer. The proposal card shows the measured value, the value the rule proposes, the method that produced it, and how it arrived at the number.
Two choices:
* **Use proposed**, which approves it. The correction is written exactly as proposed, and the value a batch calculation reads changes. The data point shows an **Approved** badge, with who approved it and when behind **Approval details**.
* **Keep current**, which rejects it and keeps the measured value. Give a reason. The proposal is closed and recorded with your reason, and the value stays as measured.
Either decision is final for that proposal.
Deciding a proposal needs write access on the account, in the app and over the API.
## What a waiting proposal blocks
A data point with an undecided proposal cannot go into a new batch. The batch generation form flags those inputs before you press anything, and **Generate** stops with a message naming how many inputs are waiting until each is approved or rejected. Batches already generated are not affected. See [Generate batches](/production-accounting/generate-batches). If you need to generate before deciding them, switch **Hold for approval** off on the rule, which withdraws them (below).
## Switching approval off
Turning **Hold for approval** off on a rule withdraws every proposal still waiting on it. Nobody approved them, so those data points keep their measured values and are not corrected unless you evaluate the period again from the rule's **Runs** tab. Data points the rule matches from then on are corrected automatically. The confirmation counts the proposals it will withdraw before you save.
## Approving corrections over the API
Proposals appear in the rule's [results](/api-reference/data-rules/results) with `approval_state` set to `pending` and a `proposed_correction` object carrying the value, the method, and its derivation. Filter the results with `approval_state=pending` to list only those, and read a rule's `pending_count` to see how many are waiting. Decide them one at a time with [approve](/api-reference/data-rules/approve) and [reject](/api-reference/data-rules/reject), which take the result's ID, or up to 100 at once with [bulk approve](/api-reference/data-rules/bulk-approve) and [bulk reject](/api-reference/data-rules/bulk-reject). All four need a token with **Data Rules** write permission. See [deciding proposals over the API](/api-reference/cookbooks/substitute-values#deciding-proposals-over-the-api) for the flow end to end.
# Common Data Rules for Data Quality Checks
Source: https://docs.mangrovesystems.com/data-rules/common-rules
Data rule examples grouped by the failure each one catches, with the condition and settings for each.
Most Mangrove projects start with the same handful of data rules, whatever the site produces. The ones below are the data quality checks teams set up first, grouped by the failure you are trying to catch, so you can start from the problem you are worried about rather than from the equipment you happen to have. Each one runs on your data as it arrives, and the values it flags are the ones worth resolving before a report reaches your auditor.
Each rule is built in the rule builder, which [Create a Data Rule](/data-rules/create-a-rule) walks through. Replace the data point names (the slugs inside `{{ }}`) and the thresholds with your own, and click **Validate** before you save to see what the rule would have flagged over the last 90 days.
Data rules are introduced in [Data Rules Fundamentals](/data-rules/fundamentals), and the operators and windows these conditions use are in [Rule Expressions](/data-rules/rule-expressions).
## Data rules by the failure they catch
**[Values that cannot be right](#values-that-cannot-be-right)**
| Rule | Catches | Needs | Action |
| - | - | - | - |
| [A value outside what the measurement can physically be](#a-value-outside-what-the-measurement-can-physically-be) | A negative mass, a percentage over 100 | Nothing extra | Alert |
| [A batch calculation result outside its physical bounds](#a-batch-calculation-result-outside-its-physical-bounds) | A batch output that went negative | A model | Alert |
**[Values that are wrong for their context](#values-that-are-wrong-for-their-context)**
| Rule | Catches | Needs | Action |
| - | - | - | - |
| [A process limit that only applies while equipment is running](#a-process-limit-that-only-applies-while-equipment-is-running) | A temperature that dropped during production | Status on the same event | Alert |
| [A product meter reporting while the plant is off](#a-product-meter-reporting-while-the-plant-is-off) | Product flow with the inlet below threshold | Both on the same event | Substitute |
| [A value that jumped away from its recent history](#a-value-that-jumped-away-from-its-recent-history) | A swing no equipment change explains | History on the data point | Alert |
| [A result far from its own baseline](#a-result-far-from-its-own-baseline) | An outlier inside its physical bounds | History on the data point | Alert |
**[Two measurements that should agree and do not](#two-measurements-that-should-agree-and-do-not)**
| Rule | Catches | Needs | Action |
| - | - | - | - |
| [Two meters that should agree drifting apart](#two-meters-that-should-agree-drifting-apart) | Inlet and outlet past their tolerance | Both on the same event, in one unit | Alert |
| [More product out than feedstock in](#more-product-out-than-feedstock-in) | A mass balance that cannot hold | Both on the same event, in one unit | Alert |
**[Instruments and feeds that stopped measuring](#instruments-and-feeds-that-stopped-measuring)**
| Rule | Catches | Needs | Action |
| - | - | - | - |
| [A meter stuck on one value](#a-meter-stuck-on-one-value) | A meter still reporting after it stopped measuring | History on the data point | Alert |
| [An analyzer value that dropped out](#an-analyzer-value-that-dropped-out) | An empty or zero value mid event | History on the data point | Substitute |
| [A month that came in under its expected coverage](#a-month-that-came-in-under-its-expected-coverage) | Missing data against the cadence you declared | Capture rate enabled, and a cadence | Alert |
## Alert or correct a value
Every data rule does one of two things when its condition matches, and it is worth deciding which you want before you pick a rule.
| Action | What happens to the value |
| - | - |
| **Alert** | The data point is flagged for review. The value is untouched, and whoever [triages the alert](/data-rules/manage-alerts) can dismiss it with a reason. |
| **Substitute** | The value is replaced with one Mangrove derives, and the original is recorded alongside it. Data Points rules only. |
Every substitute rule below also works as an alert: keep the condition and pick **Alert**.
An alert rule also chooses which data points to flag when it fires. **Automatic** flags every data point the condition names, and **Specific data points** lets you pick. Where a condition names a value and the status that gates it, picking the value keeps the alert on the thing that went wrong.
**Hold for approval** sits on top of a substitute rule. With it on, each match becomes a proposal for a person to decide before anything is written to the data. See [Approve corrections](/data-rules/approve-corrections).
## What a data rule can see
Three things decide whether a rule you write will work on your data.
* **One event at a time.** A rule reads the data points captured together on one event, the same row on the Events table, so two data points named in one condition have to arrive together. A rule naming data point types from two different event types is rejected when you save.
* **History, when you ask for it.** A condition carrying a `WITHIN` window reads earlier data points of the same type, which is what the stuck meter, spike, baseline and coverage rules below are built on.
* **Data arriving is what runs a rule.** Rules are evaluated when data is captured, imported, or corrected, and they read the data you hold. A rule set to [run only on demand](/data-rules/create-a-rule#rules-that-run-on-demand) runs when you evaluate a period instead. A feed that has stopped sending anything shows up in the [Capture Rate](/data-collection/data-capture-rate) view straight away, and a rule on its coverage reports once the feed delivers again.
## Values that cannot be right
### A value outside what the measurement can physically be
A meter counter that rolled over, a unit conversion applied twice, or a spreadsheet cell left blank all arrive as a value the measurement can never take. A biochar mass at or below zero is the plain case. Match the bound to the measurement before you use this one: a flow meter reads zero overnight while the equipment is idle, so a rule on a meter tests `< 0` and leaves zero alone.
```
{{data-point.biochar_mass}} <= 0
```
| Setting | Value |
| - | - |
| Trigger type | Data Points |
| Action | Alert |
| When this rule fires, flag | **Automatic** |
A percentage, such as feedstock moisture or methane content, takes `not between 0 and 100`. A lab result takes `not between` with the range your lab can actually report, which is narrower than the range the units allow.
This is the cheapest rule on the page to run and the one that catches the most obvious corruption, so it is worth having on every measurement that has a hard bound, not only the ones you suspect.
### A batch calculation result outside its physical bounds
A model output can go wrong while every input passes its own checks. A net removal figure at or below zero means an input is overstated somewhere upstream. So does a volume worked out by subtraction coming out negative, where the other sources have been taken off a total that was too small to start with.
The outputs a model produces are its **nodes**, which is why the variable below reads `{{node.}}` rather than naming a data point.
```
{{node.net_removals}} <= 0
```
| Setting | Value |
| - | - |
| Trigger type | Batch Calculation, with the model selected |
| Action | Alert |
Pick the model on the When step, then type `{{` in the condition to choose the output from the list. A volume worked out by subtraction takes the same rule with `< 0`.
The alert lands on the batch, so pair this rule with a Data Points rule on the inputs that can overstate, such as the [physical-bounds check](#a-value-outside-what-the-measurement-can-physically-be) above, and an alert on the data point then points at the cause.
## Values that are wrong for their context
### A process limit that only applies while equipment is running
A pyrolysis reactor spends part of every cycle heating up and cooling down, so a rule that tests the temperature alone raises a false alert on every start and stop. Adding the reactor's status to the condition limits the rule to the time production is actually running. Both data points have to be captured on the same event.
```
{{data-point.reactor_status}} = 'running' AND {{data-point.reactor_temperature}} < 500
```
| Setting | Value |
| - | - |
| Trigger type | Data Points |
| Action | Alert |
| When this rule fires, flag | **Specific data points**, with the temperature selected |
Replace `500` with the floor your process runs at, in the unit the temperature is captured in, and write the status value exactly as it appears in your data: the comparison is exact, so `'Running'` does not match `'running'`. A controller that reports status as a number takes `= 1` in place of `= 'running'`.
Flag the temperature only. With **Automatic** the status is flagged as well, and whoever [triages the alert](/data-rules/manage-alerts) is sent to a value that did nothing wrong.
The same shape suits any limit that only matters under load: a flare temperature, a kiln pressure, an engine's exhaust temperature.
### A product meter reporting while the plant is off
On a biogas or renewable natural gas (RNG) upgrading plant, the product meter can report a small flow while the inlet meter shows the plant is not running. The rule reads the inlet as the gate and the product meter as the value to correct, so both have to be captured on the same event.
```
{{data-point.inlet_flow}} < 25 AND {{data-point.rng_product_flow}} != 0
```
| Setting | Value |
| - | - |
| Trigger type | Data Points |
| Action | Substitute |
| When this rule fires, substitute | The product flow |
| Substitution methods | **Constant**, value `0` |
| Hold for approval | On |
Replace `25` with the inlet flow below which the plant is not producing, in the unit the meter is captured in. Keep **Hold for approval** on while you are confirming the threshold, so each match becomes a proposal to check against the plant log before anything is written. See [Approve corrections](/data-rules/approve-corrections).
The `!= 0` clause stops the rule matching the zero it wrote, and leaves a product meter that already reads zero alone.
The same shape suits any pair where one measurement says the other should be zero: a charger's output against its session status, a pump's flow against its run signal.
### A value that jumped away from its recent history
A value can sit inside its physical bounds and still be wrong for the hour it arrived in. A feedstock mass that swings by a third between one data point and the next, with nothing on the plant to explain it, is the case this catches. The condition compares the value against the average of its own recent history.
```
RATIO({{data-point.feedstock_mass}}, AVG({{data-point.feedstock_mass}} WITHIN TRAILING 6.hours)) NOT BETWEEN 0.67 AND 1.5
```
| Setting | Value |
| - | - |
| Trigger type | Data Points |
| Action | Alert |
| When this rule fires, flag | **Automatic** |
`RATIO` divides the first value by the second, so the band is written as multiples rather than as a difference: `NOT BETWEEN 0.67 AND 1.5` fires on a value less than two thirds or more than one and a half times its recent average. Widen the band on a measurement that moves fast, and tighten it on one that should barely move.
Size the window against the interval the meter reports at. Six hours on a 15 minute meter holds two dozen values. A window holding only a handful is pulled upward by the very value you are testing, which makes an extreme value look closer to normal than it is.
The value being tested counts in its own trailing window, so the first value on a new measurement is compared against itself, the ratio comes out at 1, and the rule does not fire.
### A result far from its own baseline
A batch result, a lab assay, or a daily average can be well inside its physical bounds and still be unlike anything the site has produced before. `ZSCORE` measures how far the value sits from the average of its window, counted in standard deviations, so it catches an outlier the bounds checks let through.
```
ZSCORE({{data-point.batch_carbon_removed}} WITHIN TRAILING 90.days) > 3
AND COUNT({{data-point.batch_carbon_removed}} WITHIN TRAILING 90.days) >= 20
```
| Setting | Value |
| - | - |
| Trigger type | Data Points |
| Action | Alert |
| When this rule fires, flag | **Automatic** |
`> 3` catches results above the baseline only. To catch both directions, write `NOT BETWEEN -3 AND 3`. Three standard deviations flags fewer values than two.
The `COUNT` holds the rule until the window has twenty data points behind it. Keep it above ten for a threshold of `3`: the tested value counts in its own window, so a window of ten or fewer cannot produce a z-score that high. `ZSCORE` also returns nothing when the window holds fewer than two data points or they are all identical, and the comparison is then false.
To ask a different question, whether a result is more than three times the trailing average rather than three standard deviations from it, use `RATIO`. The two thresholds are not equivalent, and on a steady measurement the multiple is much the looser of them:
```
RATIO({{data-point.batch_carbon_removed}}, AVG({{data-point.batch_carbon_removed}} WITHIN TRAILING 90.days)) > 3
```
[Spotting an outlier with ZSCORE](/data-rules/rule-expressions#spotting-an-outlier-with-zscore) covers the window choices.
## Two measurements that should agree and do not
### Two meters that should agree drifting apart
Two instruments measuring the same quantity should track each other: an inlet flow against an outlet flow, a mass measured on the truck against the same mass measured at the weighbridge. When they drift apart, one of them is wrong, and neither is outside its own bounds. Both meters have to be captured on the same event, both have to be numeric, and both have to be in the same unit, because the comparison reads the raw values with no conversion.
```
RATIO({{data-point.inlet_flow}}, {{data-point.outlet_flow}}) NOT BETWEEN 0.95 AND 1.05
```
| Setting | Value |
| - | - |
| Trigger type | Data Points |
| Action | Alert |
| When this rule fires, flag | **Automatic** |
The band is the tolerance you accept, as multiples: `0.95` to `1.05` allows five per cent either way. It covers the real process difference between the two measuring points as well as the instrument error.
`RATIO` returns nothing when either side has no value, or when the second is zero, so this rule does not fire on a gap.
Flag both data points with **Automatic**, so whoever triages the alert sees the pair.
### More product out than feedstock in
A mass balance is the check that needs no threshold at all: more biochar out than feedstock in is impossible, whatever the yield. A condition can compare two data points directly, with no number in it, as long as both are captured on the same event and in the same unit. The comparison reads the raw values, so a biochar mass in kilograms against a feedstock mass in tonnes fires on nearly every event.
```
{{data-point.biochar_mass}} > {{data-point.feedstock_mass}}
```
| Setting | Value |
| - | - |
| Trigger type | Data Points |
| Action | Alert |
| When this rule fires, flag | **Automatic** |
When one of the two data points is absent from an event, the condition has nothing to compare and the rule does not fire, so a pair that is only sometimes captured together is a rule that only sometimes runs.
To allow for a yield you already know, use `RATIO` with the highest yield your process can reach. This one fires when the output is more than 35 per cent of the input:
```
RATIO({{data-point.biochar_mass}}, {{data-point.feedstock_mass}}) > 0.35
```
The same shape suits any pair the physics constrains: water out against water in, energy delivered against energy metered.
## Instruments and feeds that stopped measuring
### A meter stuck on one value
A flow meter or sensor that has frozen keeps reporting on schedule, so nothing about any single value looks wrong. The rule tests that the lowest and highest values over the last hour are the same, with a count beside it saying how many identical values it takes to call the meter stuck.
```
MIN({{data-point.rng_flow}} WITHIN TRAILING 1.hour) = MAX({{data-point.rng_flow}} WITHIN TRAILING 1.hour)
AND COUNT({{data-point.rng_flow}} WITHIN TRAILING 1.hour) >= 3
AND {{data-point.rng_flow}} != 0
```
| Setting | Value |
| - | - |
| Trigger type | Data Points |
| Action | Alert |
| Flag the whole run | On. The switch appears once the condition uses an aggregate with a window, and names the run, the stretch of repeated values, once you click **Validate**. |
Size the window and the count against the meter's interval. A meter reporting every 15 minutes puts four values in an hour, so `COUNT >= 3` is met as soon as the hour is flat. A meter reporting once a minute wants a shorter window or a higher count, so a few quiet minutes do not trip it.
The `COUNT` is required, and it has to be held to at least two values. A condition without one is rejected when you save, because `MIN = MAX` is true of a single value and the rule would otherwise fire on the first value after every gap.
The last clause leaves out a flatline of zeros, which is what idle equipment produces. Drop it on a measurement that never legitimately reads zero.
[Catching a stuck sensor](/data-rules/rule-expressions#catching-a-stuck-sensor) explains the condition and what **Flag the whole run** does, and [Repair a stuck sensor](/api-reference/cookbooks/substitute-values#repair-a-stuck-sensor) writes the same rule as a substitution over the API.
### An analyzer value that dropped out
A gas analyzer that loses its connection records an empty value, or a zero, for a stretch of data points while the rest of the event is fine. The rule replaces each one with the last value the analyzer measured, and with the day's average when there is no earlier value to carry forward.
`IS MISSING` matches a data point that arrived without a value. A slot the analyzer never submitted at all has no data point for the rule to read, so a stretch where the feed sent nothing is a [capture rate question](#a-month-that-came-in-under-its-expected-coverage) rather than this rule.
```
{{data-point.methane_content}} IS MISSING OR {{data-point.methane_content}} = 0
```
| Setting | Value |
| - | - |
| Trigger type | Data Points |
| Action | Substitute |
| When this rule fires, substitute | The methane content |
| Substitution methods | **Forward fill**, then **Trailing average** over the last day |
| Hold for approval | On |
Unless **Data point to use** names another data point type, the fill methods only draw on values carrying the same tracking ID as the data point being corrected, the identifier that ties a series of data points to one piece of equipment or one delivery. A rule that alerts where you expected a correction is the cue to check the tracking IDs on the events it reads.
Forward fill takes the most recent earlier value that this rule does not match, and only a measured one, so a run of dropouts fills from the last real measurement rather than from a value already corrected. When neither method finds a value, the data point is left as it is and an alert is raised.
With **Hold for approval** on, each replacement stays a proposal until a person decides it. See [Create a Data Rule](/data-rules/create-a-rule#if-the-action-is-substitute) for the method list and what each one draws on.
### A month that came in under its expected coverage
`DATA_CAPTURE_RATE` reads the share of the data you expected that actually arrived, so a rule can flag a month that came in short. Every other rule on this page reads values you hold. This one reads how many of them turned up against the cadence you declared, which is how missing data becomes something a rule can act on.
```
DATA_CAPTURE_RATE({{data-point.methane_content}} WITHIN CALENDAR MONTH) < 0.9
```
| Setting | Value |
| - | - |
| Trigger type | Data Points |
| Action | Alert |
| When this rule fires, flag | **Automatic** |
The rule reports on a period once data arrives for it. To watch a feed that is quiet right now, and to see which slots are missing rather than how many, use the [Capture Rate](/data-collection/data-capture-rate) view.
Data capture rate is switched on per account. Ask your Mangrove account team if the **Cadence** field is not in your project.
The rate is a fraction rather than a percentage, so compare against `0.9`, not `90`. To set the limit as time rather than a share, read the same gap with `TIME_NOT_CAPTURED` and compare it with a duration, on a cadence counted in seconds, minutes, hours, days or weeks:
```
TIME_NOT_CAPTURED({{data-point.methane_content}} WITHIN CALENDAR MONTH) > 3.days
```
The rule is evaluated against the data points that did arrive, so with **Automatic** the alert lands on one of those rather than on an empty slot.
The window is required and only two resolve, `CALENDAR MONTH` and `CALENDAR YEAR`. A year reads the year to date. The data point's event type also has to declare a **Cadence**, since the cadence is what says how much data to expect; see [Data capture rate](/data-collection/data-capture-rate) for setting one. A rule that names a data point with no cadence behind it will not save.
Capture rate reads a fact about the data point type rather than a value, so it belongs on an alert rule. A substitute rule can read it only when the rule [runs on demand](/data-rules/create-a-rule#rules-that-run-on-demand), and only for a data point other than the one it corrects.
# Create a Data Rule
Source: https://docs.mangrovesystems.com/data-rules/create-a-rule
Walk through writing, validating, and saving a project data rule.
The rule builder walks you through three steps: **When** the rule should fire, **Then** what happens, and a **Preview** of what it would flag. This guide covers that flow end to end, from opening Data Rules through to saving, then editing a rule afterwards.
## Before you start
You need:
* **Write access** to the project's Data Rules. Ask an admin if the **Add data rule** button is greyed out.
* A clear sense of the **measurement or calculation** you want to validate, and the **threshold** or pattern that defines a problem.
## Step-by-step
Navigate to **Project Settings → Data Rules**. The page lists every rule configured for the project, with what it runs on, its status, and the dates it applies to.
Click **Add data rule** to start.
The **New data rule** screen offers two kinds of rule:
| Kind | When to use |
| - | - |
| **Data Points** | Runs on each data point as it is ingested or updated. Best for catching problems in raw measurements, such as out-of-range values or missing values. |
| **Batch Calculation** | Runs against model calculation results when a batch is created or updated. Best for catching downstream calculation issues, such as a CI score drifting outside expected bounds. |
Picking one starts the wizard. You can come back to this choice from the **Change** link on the When step at any point before saving. Re-picking the same kind keeps your work; switching to the other one clears the condition, because the two author against different variables.
This step holds five things, in order:
1. The **Trigger type** you picked, shown as a chip. For a Batch Calculation rule, also select the **model** whose results the rule reads.
2. The **Condition**. Type `{{` to open autocomplete and pick a data point or model node. When the condition evaluates to true on a record, that record is flagged.
```
{{data-point.carbon_content}} not between 0.50 and 0.90
```
See [Rule Expressions](/data-rules/rule-expressions) for the full language reference.
3. The **Rule name**, shown in the rules list and in audit history. Keep it brief and stable, even if you later edit the condition.
4. The **Effective period**. Choose **Always active**, or **Fixed range** to set an **Effective from** and **Effective to**. Saving evaluates every matching record inside the period, both historical and going forward, unless the rule runs on demand (below). Records outside it are not touched.
5. **Run only on demand**. Off by default. Turn it on for a rule that should run only when you evaluate a period, rather than on every data point as it arrives. See [Rules that run on demand](#rules-that-run-on-demand).
**Next** stays disabled until the step has what it needs. Hover it to see which fields are missing.
Data Points rules apply across the whole project. Autocomplete offers every data point type that is captured on an event, and the data points that get flagged come from the condition itself. A calculated data point type, one a model computes, is not offered, and a condition naming one is rejected when you save. Test a computed value with a Batch Calculation rule.
One rule reads a single event type. Autocomplete does not enforce that, so a condition can be written across two event types and is rejected when you save, naming the event types it spans.
Pick the **Action**:
| Action | What it does |
| - | - |
| **Alert** | Flag only. No value change. |
| **Substitute** | Replace the value with the first method that produces one. |
**Substitute** appears only on Data Points rules. See [Substituting a value](/data-collection/data-points#substituting-a-value) for what a correction does to the data.
### Acting on the whole window
When the condition contains a windowed aggregate, a switch appears under the action. With the switch off, the rule acts on the most recent data point in the window, the one that made the condition true. With it on, the rule acts on the other data points in the window as well: an alert rule flags them, a substitute rule corrects them.
The switch reads **Flag the whole window** or **Correct the whole window**. On a stuck-sensor condition, one that tests `MIN = MAX` over one data point and window, it reads **Flag the whole run** or **Correct the whole run** instead, where the run is the stretch of repeated values. The first data point of the run keeps its measured value, and the fill and trailing methods leave the run out when they look for a replacement, so the replacement is never the stuck value itself. See [Catching a stuck sensor](/data-rules/rule-expressions#catching-a-stuck-sensor) for the condition and why the first value is kept.
Where the condition uses two windows, the rule acts only on the data points inside both.
### If the action is Substitute
Name the value the rule corrects under **When this rule fires, substitute**. This is a single choice and it is required. The list offers the number data points your condition references, and picks for you when there is only one to pick, so a condition reading one number and one text value fills it in for you too.
Then build the chain under **Substitution methods**. Methods are tried in order until one produces a value. Click **Add substitution method** to append one, click a row to configure it, and use **Move up**, **Move down**, and **Remove step** to arrange them.
| Method | What it uses | What you set |
| - | - | - |
| **Constant** | A fixed value you set. | **Value** |
| **Forward fill** | The most recent earlier value that passes this rule. | nothing |
| **Backward fill** | The nearest later value that passes this rule. | nothing |
| **Cross fill** | Another data point type's value for the same period, from the same event, or at the same asset when it is captured on another event type. | **Data point to use** |
| **Trailing average** | The average of the valid values in the window. | **Window** |
| **Trailing min** | The lowest valid value in the window. | **Window** |
| **Trailing max** | The highest valid value in the window. | **Window** |
| **Percentile** | The chosen percentile of the valid values in the window, such as the 90th. Needs at least two values to draw on. | **Percentile**, **Window** |
Every method except **Constant** also has a **Data point to use**. It starts on the data point being corrected; pick another **Number** data point type to draw the replacement from that one instead. Values are copied with no conversion, so the source has to be in the same unit as the value being corrected, unless it is a **Cross fill** reading from the same event. A source captured on another event type is read at the same asset.
The **Window** control offers two kinds. **Trailing** is a span reaching back from the value being corrected: set how many, and a unit from seconds to years. **Calendar** is the value's own period: set the unit, days to years, how many consecutive periods it covers, and how many periods back it starts. A calendar window one period back is the previous complete month, quarter or year, which is how you say "last month's average" rather than "this month so far".
A calendar window over the value's own period also has a **Covers** setting. **Month to date** (or the matching unit) is the default and reads the period up to the value being corrected. **Whole month** reads the entire period, including data points after the one being corrected, so a gap is filled from the month's full average. **Whole month** is available only on a single period with no periods back, and only on a rule that runs on demand.
With **Data point to use** left on the data point being corrected, the fill and trailing methods only draw on values carrying the **same tracking ID** as the one being corrected. On a project that sets a tracking ID per delivery or per ticket, that usually leaves nothing to draw on, so the rule raises an alert instead of correcting. If a substitute rule corrects nothing and you expected it to, check the tracking IDs on the events it reads. With another data point type picked, they read that type's values at the same asset instead, whatever their tracking ID.
The last row of the chain is always **Flag as alert**: if no method produces a value, the value is left as it is and an alert is raised. Adding a **Constant** changes that row to say the alert will not fire, because a constant always produces a value. Anything you place after a constant is struck through and marked as never running.
Forward fill, backward fill and Constant drop out of the picker once used, since repeating one can only fail where it failed the first time. The other methods stay selectable because their settings change what they read, so a second Trailing average over the same window is only caught when you save.
Optionally fill in **If no method produces a value, alert with** to word that alert yourself. Left blank, it states that the correction could not be applied.
### Holding corrections for approval
Below the chain, **Hold for approval** decides whether the rule writes its corrections or proposes them. With it off, a correction is written as soon as a method produces a value. With it on, each match becomes a proposal: the data point keeps its measured value until someone approves or rejects it, and a data point with a proposal waiting cannot go into a new batch. See [Approve corrections](/data-rules/approve-corrections) for deciding them.
Turn it on for a rule you are still learning to trust, or where each replacement value should be signed off. A rule that corrects many data points raises as many proposals. It is off on every new rule.
### If the action is Alert
Decide which data point the alert attaches to, under **When this rule fires, flag**:
| Option | Effect |
| - | - |
| **Automatic** | Any data point the condition references can be flagged. The eligible ones are listed underneath. |
| **Specific data points** | You pick which of the referenced data points can be flagged. Only those raise an alert; the rest are read as context. |
Reach for **Specific data points** when a rule reads one value but is really about another. A rule like `{{data-point.vent_1_speed}} > 90 OR {{data-point.vent_2_speed}} > 90` covers two pieces of equipment, so flagging both when only the first breached sends whoever triages the alert to the wrong place.
You can only pick data points the condition already references. If you later edit the condition and drop one, it is removed from the selection too. Batch Calculation rules do not use this control.
Write the **Alert message**, the explanation shown on each flagged record. It is generated from the condition by default; edit it to read naturally for the people triaging alerts.
The rule name and condition sit above this step read-only. **Edit expression** takes you back to the When step to change the condition.
On a substitute rule this step reads **What this rule would correct**, or **What this rule would propose** with **Hold for approval** on, and it shows which records the condition matches. It does not compute the replacement values; save the rule and read what it produced.
The preview stays empty until you click **Validate**. Validating checks that the condition parses and that every variable resolves, then runs the rule against the project's recent data under the heading named above.
A preview covers the last **90 days** unless you give it a range of your own, and a wider range is honoured when you do.
Read the result before saving:
* **Matches listed.** Check they are the records you expected. Unexpected matches mean the condition is too broad.
* **Nothing matches yet.** The rule is valid and will run on new data as it arrives, or, on a rule that runs on demand, when you evaluate a period. Nothing in the window matches it. A range filter appears with this result, so widen the window there, or adjust the condition, to see historical matches.
* **A sampling note.** On projects with a lot of events the preview covers only the most recent ones. Save the rule to evaluate the full history.
A condition using `WITHIN` previews the same result it will produce once saved. Windowed conditions are heavier to work out, so the sampling note above appears on a smaller project than it would for a rule without one. See [Rule Expressions](/data-rules/rule-expressions) for the detail.
The preview applies the same validation as saving, so a condition the preview accepts is one the rule builder will save.
Click **Save**. For a rule that runs as data arrives, Mangrove confirms that saving will evaluate everything the rule matches, then evaluates every matching record inside the effective period. On a substitute rule the confirmation says the values it corrects will be overwritten, or, with **Hold for approval** on, that matches become proposals and nothing changes until someone approves.
Editing a saved substitute rule applies the new methods going forward. Values it already corrected keep the value they have; revert those from the data point if they need to change.
You land on the rule detail page, showing its status, effective period, and the records found during evaluation. Unless **Run only on demand** is on, the rule is live: every new data point or batch generation inside the effective period runs the condition, and the rule alerts or substitutes as its action says.
A rule with **Run only on demand** on evaluates nothing on save and does not run as data arrives. It runs over a period when you start one from its **Runs** tab with **Evaluate a period**.
## Rules that run on demand
**Run only on demand** runs a rule over a period when you start it, rather than as data arrives. Use it for checks that read a whole period, such as a month's capture rate or a gap filled from the whole month's average, so the rule runs once the period is complete.
An on-demand rule:
* Is not evaluated when you save it, or when new data points arrive. A Batch Calculation rule that runs on demand is not evaluated when its model run finishes.
* Runs over a period when you click **Evaluate a period** on its **Runs** tab, or start a run through the API with [Re-evaluate a rule over a period](/api-reference/rule-runs/create).
* Shows **Runs on demand** on the rule's page.
Two substitute rule settings need it:
* A **Whole month** (or whole quarter, whole year) window, because it reads data points after the one being corrected. A data point whose period has not ended when the run starts is left uncorrected, and its alert says to evaluate the rule again after the period closes. While a rule has a whole-period window, **Run only on demand** cannot be turned off. Change the window first.
* A condition reading `DATA_CAPTURE_RATE` or `TIME_NOT_CAPTURED` of a data point other than the one being corrected. A substitute rule cannot read the capture rate of the data point it corrects. See [Data capture rate](/data-rules/rule-expressions#data-capture-rate).
## How rules appear in the list
The **Runs on** column shows what each rule binds to: `Data Points`, or `Batch Calculation` with the model name.
## Editing a rule
Open a rule from the Data Rules list and click **Edit**. Editing uses a single form rather than the wizard, with the same fields in the same order.
Changing the condition saves a new version and re-evaluates matching records inside the effective period. The new version's results replace the rule's existing alerts, so earlier alerts, including any you dismissed, are reset.
Changing the effective dates saves a new version too, and so does changing the action, the substitution methods, or the whole-window switch. The same reset applies to each, so plan on re-triaging a rule's alerts after any of these edits.
Switching **Hold for approval** on or off does not save a new version. Switching it off withdraws every proposal still waiting on the rule: nobody approved them, so those data points keep their measured values and are not corrected unless you evaluate the period again from the rule's **Runs** tab. The confirmation counts them before you commit.
Variables in an alert message render as the configured data point type name. The message is stored when you save, so this applies to rules created or re-saved from now on. Existing rules keep the message they were saved with.
## Stopping or deleting a rule
| Action | Effect |
| - | - |
| **Shorten the effective period** | Set or pull in the **Effective to** date so the rule stops evaluating new records. The rule and its history stay in the project, and its active alerts are re-evaluated against the new period, which resets any dismissals. Use when the rule has served its purpose for past data. |
| **Delete** | Permanently removes the rule along with all of its evaluations and alerts. Use when the rule was a mistake or you no longer need it. Values the rule corrected stay corrected and keep the rule's name and ID in their history. To put them back first, use [Revert a rule's corrections](/api-reference/data-rules/revert-corrections) in the API. A delete is refused while an evaluation or revert is running for the rule, so try again once it finishes. |
# Data Rules Fundamentals
Source: https://docs.mangrovesystems.com/data-rules/fundamentals
Catch data quality issues automatically with project-defined rules that flag events and batch calculations.
Data Rules let you encode the data quality checks that matter to your project as expressions Mangrove evaluates automatically. When an event is created, a data point is updated, or a batch is calculated, the rule runs in the background and acts on any record matching a condition you've defined. A rule set to [run only on demand](/data-rules/create-a-rule#rules-that-run-on-demand) runs instead over a period you start.
Use Data Rules when you want to:
* Catch out-of-range measurements before they propagate into reports
* Flag missing or implausible values during data ingestion
* Correct a recurring class of invalid or missing data points without editing each one
* Validate that calculated batch outputs stay within expected bounds
* Encode methodology guardrails that your team would otherwise check by eye
[Commonly used rules](/data-rules/common-rules) collects them grouped by the failure each one catches, ready to adapt to your project's data points.
## Key concepts
#### Rule
A **rule** is a single named expression evaluated against either incoming event data or model calculation results. Rules belong to a project and are managed in **Project Settings → Data Rules**.
Every rule carries an ID beginning `rule_`, which is how the API identifies it. The rules list hides the ID until you add the **Rule ID** column from **Columns**, and the search box matches a rule's name or its ID, so an ID you have been given finds its rule.
#### What a rule runs on
Each rule runs on one kind of data, shown in the rules list under **Runs on**:
| Runs on | Evaluates |
| - | - |
| **Data Points** | Individual data points as they are ingested or updated, or over a period you start for a rule that runs on demand. Use this to validate raw measurements at ingestion. |
| **Batch Calculation** | Outputs of a model after a batch is created or updated. Use this to validate downstream calculation results. |
A rule never blocks event creation, data point updates, or batch generation. It acts after the fact.
#### What a rule does
Each rule takes one of two actions on the records its condition matches:
| Action | Effect |
| - | - |
| **Alert** | Flags the record for review. The data is untouched, and the alert can be dismissed with a reason. |
| **Substitute** | Replaces the matching value with one Mangrove derives, and records the original alongside it. Data Points rules only. Can hold each correction for a person to approve before it is written. |
A substitute rule carries an ordered list of methods, and works down it until one produces a usable value. Where none does, the rule raises an alert instead. With **Hold for approval** on, the rule proposes each correction and a person decides it; see [Approve corrections](/data-rules/approve-corrections). See [Substituting a value](/data-collection/data-points#substituting-a-value) for what a correction looks like on the data, and [Correcting many values at once](/api-reference/cookbooks/substitute-values) for writing one over the API.
#### Rule expression
The **expression** is the condition that triggers the rule. Expressions are written in a domain-specific language similar to SQL, for example:
```
{{data-point.carbon_content}} not between 0.50 and 0.90
```
When the expression evaluates to true on a record, that record is flagged. See [Rule Expressions](/data-rules/rule-expressions) for the full reference.
#### Effective period
Every rule has an **Effective from** date and an optional **Effective to** date. The rule evaluates records whose date falls within this range. Anything outside it is left alone.
* Set **Effective to** to a date when the rule should stop applying (for example, when a methodology version retires).
* Leave **Effective to** blank to mean **Ongoing**: the rule keeps running on new data indefinitely.
When you save a new or edited rule, Mangrove evaluates every matching record within the effective range automatically. There is no separate backfill step. A rule that [runs on demand](/data-rules/create-a-rule#rules-that-run-on-demand) evaluates nothing on save and runs over the period you start.
#### Alert
When a rule's expression matches a record within the effective period, Mangrove creates an **alert**. Alerts surface in three places:
* **Event drawer**: alerts appear on the affected event with a link back to the rule
* **Batch detail**: batches with rule alerts show them on the batch page
* **Reports**: alerts on records included in a report are propagated to that report
Alerts can be **dismissed** with a reason, which preserves the result for audit while clearing it from the active list.
## Lifecycle
```mermaid theme={null}
flowchart LR
A[Draft expression] --> B[Validate / test]
B --> C[Set effective period]
C --> D[Save rule]
D --> E[Evaluates records in range]
E --> F[Edit → new version, re-evaluates]
E --> G[Shorten effective period → stops on new data, resets dismissals]
E --> H[Delete → removes rule and all alerts]
```
To stop a rule from running on new data, set or shorten its **Effective to** date. The rule and its history stay in the project, and its alerts are re-evaluated against the new period, which resets any dismissals, so plan on re-triaging afterwards. To remove a rule entirely, **delete** it, which also removes all of its evaluations and alerts. Values it corrected stay corrected.
## API access
Reading rules and their results over the [Mangrove API](/api-reference/authentication) requires your API token to have **Data Rules** read permission. Changing anything, including creating a rule, editing it, deleting it, dismissing its alerts, and approving or rejecting a correction it proposed, requires write permission. A token without them gets a `403 Forbidden` back. To grant them, open **Account Admin → API**, edit the token, and tick **Data Rules** in the permissions list.
The full set of endpoints is under [Data Rules](/api-reference/data-rules/get-all) in the API reference.
# Manage Rule Alerts
Source: https://docs.mangrovesystems.com/data-rules/manage-alerts
Review, dismiss, and act on alerts raised by data rules across events, batches, and reports.
When a data rule's expression matches a record, Mangrove raises an **alert**. This page covers where alerts surface, how to triage them, and how to act on them without losing audit history.
## Where alerts surface
Alerts from data rules appear in three places:
| Location | What you see |
| - | - |
| **Event Detail drawer** | Events flagged by a Data Points rule show an alert card here, with the alert message, the rule name, and the rule's effective period. |
| **Batch detail** | Batches flagged by a Batch Calculation rule list their rule alerts in the batch detail page. The card surfaces the effective period range so you can scope the issue. |
| **Reports** | Alerts on records included in a report propagate to that report. The report flags how many alerted records it contains so reviewers can address them before signing off. |
You can also see all triggered records for a single rule by opening **Project Settings → Data Rules** and clicking into the rule.
## Triage an alert
From an Event Detail drawer, batch page, or rule details page, click the alert card. The card shows:
* The **alert message**, or the rule name where the rule has no message
* The **rule name** and the rule's **effective period**
* A **See N duplicates** link, where the rule clustered several data points together
The expression itself is on the rule detail page, in **Project Settings → Data Rules**, with its variables highlighted.
Three choices:
* **Fix the data.** If the alert is real, correct the underlying data point or model input. Re-running the calculation re-evaluates the rule; if the data is now in range, the alert is automatically resolved.
* **Dismiss the alert.** If the alert is a known false positive or doesn't apply to this record, dismiss it with a reason. The alert is hidden from the active list but preserved for audit.
* **Decide a proposed correction.** A substitute rule holding its corrections for approval raises a proposal rather than an alert. Approve or reject it from the same drawer; see [Approve corrections](/data-rules/approve-corrections).
* **Adjust the rule.** If the alert reveals that the rule itself is wrong (too aggressive, wrong threshold), edit the rule expression. Editing re-evaluates the rule against the project's data and replaces its existing alerts with the new results. The previous alerts are not kept.
Click **Dismiss alert** and provide a reason. The reason is stored alongside the alert and visible to anyone reviewing the rule's history or the affected report. Dismissed alerts appear with a `dismissed` tag rather than `fail`.
Dismissing an alert does not change the underlying data point or batch. It only changes how the alert is surfaced. If the data is recalculated and the rule still matches, a fresh alert can be raised.
## Reviewing rule activity
Open a rule from **Project Settings → Data Rules** to see its full evaluation history:
* **Alerted Records**: every record the rule currently matches, with status (`fail`, `pass`, `dismissed`), date range, tracking ID, and source.
* **Test rule**: re-run the expression against recent project data without saving anything new. Useful when you want to gauge whether a rule is still firing as expected.
For Data Points rules, click a record to open the Event Detail drawer. For Batch Calculation rules, click a batch to open the batch preview drawer with the calculation context.
## Stopping a noisy rule
If a rule is producing alerts you don't want to act on:
* **Shorten the effective period.** Set or pull in the **Effective to** date so the rule stops applying to new records. This saves a new version of the rule, so its active alerts are re-evaluated and any dismissals you made are reset along with them.
* **Edit the expression.** Tighten or loosen the rule. Editing re-evaluates the rule and replaces its existing alerts with the new results. The previous alerts are not kept.
* **Delete the rule.** Removes the rule and all of its alerts from the project.
Choose **shorten the effective period** when the rule has served its purpose for past data, **edit** when the threshold or condition needs tuning, and **delete** when the rule is no longer relevant to the project at all. The first two both reset the rule's dismissals, so do your triage after the change rather than before it.
## Alerts and reports
Alerts on records included in a report are visible to the report reviewer. To resolve them before submitting a report:
1. Address the underlying data issue, or
2. Dismiss the alert with a reason that the reviewer will see.
Either path produces an audit-friendly record of how the issue was handled.
# Rule Expressions
Source: https://docs.mangrovesystems.com/data-rules/rule-expressions
Reference for the rule expression language: variables, operators, aggregates, time windows, and the stuck-sensor, outlier, drift and capture-rate checks.
Rule expressions are written in a small expression language with familiar comparison and logical operators. Each expression is a **condition**. When it evaluates to true on a record, the rule acts on that record: an alert rule flags it, a substitute rule corrects it.
```
{{data-point.carbon_content}} not between 0.50 and 0.90
```
This page is the reference for the full set of operators and patterns. For a walkthrough of writing your first rule, see [Create a Data Rule](/data-rules/create-a-rule).
## Variables
Variables reference the data Mangrove evaluates the expression against. They are wrapped in double curly braces and always carry a prefix naming what kind of thing they point at.
| Form | Used in | Points at |
| - | - | - |
| `{{data-point.}}` | Data Points rules | A data point on the event being evaluated. |
| `{{node.}}` | Batch Calculation rules | A node in the selected model. |
| `{{node..}}` | Batch Calculation rules | A specific data point type on that node, when the node name alone is ambiguous. |
```
{{data-point.carbon_content}}
{{node.total_emissions}}
{{node."Biochar Produced".carbon_content}}
```
Use double quotes around a node name that contains spaces or other special characters, as in the third example.
The prefix has to match what the rule runs on. A Data Points rule cannot reference `{{node.*}}`, and a Batch Calculation rule cannot reference `{{data-point.*}}`. Mismatches are rejected when you save.
What the editor offers follows from that:
* **Data Points** rules use data point slugs. These rules apply across the whole project, so autocomplete offers every data point type that is captured on an event, and the rule evaluates against any event whose data points satisfy the condition. One rule reads a single event type: a condition referencing types from two of them is rejected when you save, and autocomplete does not narrow the list to stop you writing one. A calculated data point type, one a model computes rather than an event captures, is not offered, and a condition that names one is rejected when you save. Test a computed value with a Batch Calculation rule and a `{{node.}}` reference instead.
* **Batch Calculation** rules use nodes in the selected model.
Type `{{` in the rule editor to open autocomplete. Each suggestion shows the variable name and its description, value type, or unit when available, and inserts the full prefixed form for you.
## Comparison operators
| Operator | Meaning |
| - | - |
| `=` | Equal to |
| `!=` | Not equal to |
| `<` | Less than |
| `<=` | Less than or equal to |
| `>` | Greater than |
| `>=` | Greater than or equal to |
```
{{data-point.ash_content}} > 0.05
{{data-point.batch_status}} != 'verified'
{{data-point.biochar_mass}} > {{data-point.feedstock_mass}}
```
The right side takes a literal or a second data point, as in the third example, which is how a mass balance or any other check between two measurements is written without a threshold. Both data points have to be captured on the same event: when one of them is absent from an event, the comparison has nothing to read and the rule does not fire.
## Logical operators
Combine conditions with `AND`, `OR`, and `NOT`. Use parentheses to group. `NOT` binds tightest, then `AND`, then `OR`.
```
{{data-point.ash_content}} > 0.05 AND {{data-point.moisture}} < 0.10
NOT ({{data-point.feedstock_type}} = 'manure' OR {{data-point.feedstock_type}} = 'crop_residue')
```
## Range and set operators
| Operator | Example | Triggers when... |
| - | - | - |
| `BETWEEN ... AND ...` | `{{data-point.ph}} between 6.0 and 8.0` | The value is within the range (inclusive). |
| `NOT BETWEEN ... AND ...` | `{{data-point.ph}} not between 6.0 and 8.0` | The value is outside the range. |
| `IN (...)` | `{{data-point.country}} in ('US', 'CA', 'MX')` | The value matches any item in the list. |
| `NOT IN (...)` | `{{data-point.country}} not in ('US', 'CA')` | The value matches none of the items in the list. |
## Text operators
| Operator | Example | Triggers when... |
| - | - | - |
| `CONTAINS` | `{{data-point.notes}} contains 'override'` | The text contains the substring. |
| `STARTS WITH` | `{{data-point.tracking_id}} starts with 'TMP-'` | The text starts with the prefix. |
| `ENDS WITH` | `{{data-point.filename}} ends with '.csv'` | The text ends with the suffix. |
| `MATCHES` | `{{data-point.lot_code}} matches '[A-Z]{2}-[0-9]+'` | The text matches the regular expression. |
String literals use **single quotes**. `MATCHES` patterns use Ruby regular expression syntax, and each match attempt is bounded by a one-second timeout, so keep patterns anchored and avoid ones that backtrack heavily.
There is no `NOT MATCHES` operator, and the same goes for the other text operators. Negate them with the `NOT` prefix instead: `NOT {{data-point.lot_code}} matches '[A-Z]{2}-[0-9]+'`.
## Presence operators
Use these to check whether a value was recorded.
| Operator | Triggers when... |
| - | - |
| `IS MISSING` | The data point holds no value at all. |
| `IS PRESENT` | The data point holds a value that is not empty text and not `false`. |
```
{{data-point.lab_sample_id}} is missing
{{data-point.adjustment_note}} is present AND {{data-point.adjustment_note}} = 'manual'
```
Two cases fall between the two operators, and neither one catches them. A text data point holding an empty string is not missing, because a value was stored, and it is not present either. A boolean holding `false` behaves the same way. Test for those with `= ''` and `= false` instead.
A value that was never submitted has no data point for the rule to evaluate, so `IS MISSING` stays out of its reach.
## Aggregates
Aggregate functions roll a set of values into a single number. Use one as the left side of a comparison.
| Function | Returns |
| - | - |
| `COUNT` | Number of values |
| `SUM` | Sum of values |
| `AVG` | Average of values |
| `MIN` | Smallest value |
| `MAX` | Largest value |
| `ZSCORE` | How far the evaluated value sits from the window's average, in standard deviations. Always takes a window; see [Spotting an outlier](#spotting-an-outlier-with-zscore). |
```
SUM({{data-point.batch_input_mass}}) > 1000
AVG({{data-point.moisture}}) not between 0.05 and 0.15
COUNT({{data-point.lab_sample_id}}) < 3
```
`SUM`, `AVG`, `MIN`, `MAX` and `ZSCORE` need a numeric data point type and are rejected when you save otherwise. `COUNT` works on any type.
By default an aggregate covers only the data points on the event being evaluated. To ask a question that spans events, add a window.
To compare an aggregate against another measurement, or to hold two measurements to a tolerance band, see [Catching drift between two data points with RATIO](#catching-drift-between-two-data-points-with-ratio).
## Windowed aggregates
Add a `WITHIN` clause inside the aggregate's parentheses to widen the set from one event to many. Use it for rules about how often something happens, or about a total across a period.
```
COUNT({{data-point.temperature}} WITHIN ROLLING 1.week) > 3
MIN({{data-point.flow_rate}} WITHIN TRAILING 1.hour) = MAX({{data-point.flow_rate}} WITHIN TRAILING 1.hour) AND COUNT({{data-point.flow_rate}} WITHIN TRAILING 1.hour) >= 3
SUM({{data-point.emissions}} WITHIN CALENDAR YEAR) > 1000
```
Three modes are available. Pick the one that matches the question you are asking.
| Mode | The window is | Ask it when |
| - | - | - |
| `ROLLING` | A span reaching the duration both before and after the data point. | Time proximity matters in either direction, such as a cluster of values. |
| `TRAILING` | A span reaching the duration back from the data point, and no further forward. | The question is about what came before the value, such as whether a sensor has been stuck on one value. |
| `CALENDAR` | The clock bucket the data point falls in. | Reporting periods matter, such as one delivery per month. |
### ROLLING
A sliding window centred on the data point being evaluated, reaching the full duration in *both* directions. For a value captured at a single point in time, `ROLLING 7.days` therefore spans 14 days: the seven days before it and the seven after. Where the event carries a start and an end, the window reaches out from both, so the span is the event's own duration plus the window twice. Use it for proximity and anomaly questions, such as flagging a cluster of data points around one point in time.
```
COUNT({{data-point.temperature}} WITHIN ROLLING 7.days) > 3
AVG({{data-point.temperature}} WITHIN ROLLING 24h) > 100
SUM({{data-point.volume}} WITHIN ROLLING 30.days) > 1000
```
Durations take two forms:
| Form | Units | Example |
| - | - | - |
| `.` | `second`, `minute`, `hour`, `day`, `week`, `month`, `year` (singular or plural) | `1.week`, `30.days` |
| `` | `s` seconds, `m` minutes, `h` hours | `24h`, `90m` |
A `ROLLING` duration has to be greater than zero. `ROLLING 0.days` is rejected when you save.
### TRAILING
A window that ends at the data point being evaluated and reaches back the duration, so it never reads a value captured later. `TRAILING 1.hour` on a value captured at 14:00 covers 13:00 to 14:00. Where the event carries a start and an end, the window reaches back from the start and ends at the end. Use it for conditions that only the history up to that point can answer, such as a sensor that has stopped changing.
```
MIN({{data-point.flow_rate}} WITHIN TRAILING 1.hour) = MAX({{data-point.flow_rate}} WITHIN TRAILING 1.hour) AND COUNT({{data-point.flow_rate}} WITHIN TRAILING 1.hour) >= 3
AVG({{data-point.temperature}} WITHIN TRAILING 24h) > 100
```
`TRAILING` takes the same duration forms as `ROLLING`, and the duration has to be greater than zero in the same way.
### CALENDAR
The calendar bucket the evaluated data point falls in, in your account's timezone. Use it for cohort and reporting-period questions, such as "no more than one of these per calendar month".
```
COUNT({{data-point.biochar}} WITHIN CALENDAR MONTH) > 1
SUM({{data-point.emissions}} WITHIN CALENDAR YEAR) > 1000
COUNT({{data-point.batches}} WITHIN CALENDAR WEEK) > 5
```
Units are `SECOND`, `MINUTE`, `HOUR`, `DAY`, `WEEK`, `MONTH`, and `YEAR`. Weeks start on Monday. The sub-hour units suit duplicate detection at a known cadence, such as one data point per clock minute.
Calendar buckets are absolute. February 27 and March 2 sit in different `MONTH` buckets and never pair, however close together they are. Two data points two seconds apart at 12:00:59 and 12:01:01 fall in different `MINUTE` buckets for the same reason. Use `ROLLING` when you care about elapsed time in either direction, `TRAILING` when only earlier values may count, and `CALENDAR` when you care about the clock bucket.
### What to know before you use one
* **The evaluated data point is in the set when the window names its own type.** `COUNT(...) = 1` is therefore true for a data point with no neighbours, so write `COUNT(...) > 1` to mean "more than just this one". In a rule that references several types, a value is not a member of a window over a *different* type.
* **The preview evaluates windowed aggregates.** It resolves each window the same way a saved rule does, evaluating once per data point rather than once per event, so what you see in the preview is what the rule will do. Because a windowed condition costs more to resolve, the preview works through it in smaller batches, which means the sampling note in [Create a Data Rule](/data-rules/create-a-rule) appears on a smaller project than an unwindowed rule would need.
* **Windows only work on `{{data-point.*}}` variables.** A `WITHIN` clause on a `{{node.*}}` reference is rejected when you save, so Batch Calculation rules cannot use one.
* **A value covering a span of time is in the window only if it overlaps it.** A data point that ends exactly as the window opens, or starts exactly as it closes, is left out. For a meter that reports a total every 15 minutes, a `CALENDAR HOUR` bucket holds the four totals inside that hour and not the one that ended as it began. A value recorded at a single moment on the window's edge is in it. A substitution method's window follows the same boundary rule.
* **The rule's effective dates clamp the window.** A `CALENDAR MONTH` bucket is cut short if the rule only became effective partway through that month.
* **You can use more than one windowed aggregate in a rule.** Each resolves its own set independently.
* **A windowed rule can act on the whole window, not only the value that fired.** That is a setting on the rule's Then step rather than part of the expression. See [Acting on the whole window](/data-rules/create-a-rule#acting-on-the-whole-window).
## Catching a stuck sensor
A sensor frozen on one value, a flatline, keeps reporting on schedule, so nothing about any single value looks wrong. The check is that the lowest and highest values over a trailing window are the same, with a count beside it saying how many identical values it takes to call the sensor stuck.
```
MIN({{data-point.flow_rate}} WITHIN TRAILING 1.hour) = MAX({{data-point.flow_rate}} WITHIN TRAILING 1.hour)
AND COUNT({{data-point.flow_rate}} WITHIN TRAILING 1.hour) >= 3
```
The `COUNT` is required. `MIN = MAX` is true of a single value, so without it the rule would fire on the first value after every gap. A `MIN = MAX` test with no windowed `COUNT` on the same data point is rejected when you save, and the preview rejects it too. The window sets how long the flatline has to last before the rule fires, and the `COUNT` stops it firing on a lone value after a gap in the data. Size the count against the meter's reporting interval: a meter reporting every 15 minutes puts four values in an hour, so `COUNT >= 3` is met as soon as the hour is flat.
The rule fires on the first value that has a flat hour behind it, and on each later value that does, for as long as the sensor stays stuck. The first value of the run (the run is the stretch of repeated values) is left alone, because the sensor was still working when it reported it. To have the rule flag or correct every repeat in the run rather than only the most recent one, turn on **Flag the whole run** or **Correct the whole run** on the Then step. See [Acting on the whole window](/data-rules/create-a-rule#acting-on-the-whole-window), and [repair a stuck sensor with a substitute rule over the API](/api-reference/cookbooks/substitute-values#repair-a-stuck-sensor) for the same rule written as a request.
The `COUNT` may use a longer window than the `MIN` and `MAX`, to count values over a day while testing flatness over an hour. The rule then acts only on the data points inside both windows.
Values that alternate, such as `412.6` and `412.7`, never trip it, and a flat run of zeros is detected like any other value. Equipment that is legitimately off, such as a flare idle overnight, is also a flat run of zeros, so exclude it with a clause like `AND {{data-point.flow_rate}} != 0`, or use a window longer than the longest idle stretch you expect.
## Spotting an outlier with ZSCORE
`ZSCORE` returns the z-score of the evaluated value: the number of standard deviations it sits from the average of the window, negative when the value is below the average. It asks how unusual a value is for that data point type. A methane content that is inside its permitted range but two standard deviations away from the month's typical value is the kind of thing it catches.
```
ZSCORE({{data-point.methane_content}} WITHIN CALENDAR MONTH) NOT BETWEEN -2 AND 2
ZSCORE({{data-point.temperature}} WITHIN TRAILING 7.days) > 3
```
`ZSCORE` always needs a window. Without one it would have only the evaluated value to compare against, so a bare `ZSCORE(...)` is rejected when you save. Any of the three window modes works.
When the window holds fewer than two values, or every value in it is identical, there is no spread to measure. `ZSCORE` then returns nothing and the comparison is false, so the rule never fires on a lone value or a flat run. A `CALENDAR MONTH` window is thin in the first days of the month: the evaluated value counts in its own window, so on the second day it is judged against one neighbour. To require a minimum number of values in the window, add a `COUNT` over the same window, `ZSCORE({{data-point.methane_content}} WITHIN CALENDAR MONTH) NOT BETWEEN -2 AND 2 AND COUNT({{data-point.methane_content}} WITHIN CALENDAR MONTH) >= 10`, or use `WITHIN TRAILING 30.days`, which always holds a full month of history. For a substitute rule built on `ZSCORE`, see [replace an outlier and hold it for approval](/api-reference/cookbooks/substitute-values#replace-an-outlier-and-hold-it-for-approval).
## Catching drift between two data points with RATIO
`RATIO` divides one measurement by another, so a rule can test a data point against a second stream or against its own history. Use it to catch two meters that should agree drifting apart, or a week's average sliding away from the year's.
```
RATIO({{data-point.inlet_flow}}, {{data-point.outlet_flow}}) NOT BETWEEN 0.95 AND 1.05
RATIO(AVG({{data-point.methane_content}} WITHIN ROLLING 7.days), AVG({{data-point.methane_content}} WITHIN CALENDAR YEAR)) < 0.85
```
Each side is a data point reference or an aggregate over one. A plain number is not accepted on either side.
The two sides can carry different windows. A short window over a long one measures drift: it asks whether the recent picture has moved away from the baseline.
Both sides must name a numeric data point type, and both must sit on the same event type, since a rule reads one event type. Two meters recorded on separate event types cannot be compared in one rule. A text or true/false type is rejected when you save.
`RATIO` returns nothing when either side has no value, and when the denominator is zero. The comparison is false in both cases, so a rule built on `RATIO` does not fire on a gap.
## Data capture rate
`DATA_CAPTURE_RATE` asks how much of the data you expected actually arrived, so a rule can flag a period that came in short rather than a value that looks wrong. It reads a fact about the data point type itself rather than aggregating values, so it goes on the left of a comparison.
```
DATA_CAPTURE_RATE({{data-point.ambient-temperature-c}} WITHIN CALENDAR YEAR) > 0.95
DATA_CAPTURE_RATE({{data-point.ambient-temperature-c}} WITHIN CALENDAR MONTH) < 0.8
```
`DATA_CAPTURE_RATE` returns a **fraction rather than a percentage**, so compare against `0.95`, not `95`.
The `WITHIN` clause is required, because a capture rate with no period behind it means nothing. Only two windows resolve, `CALENDAR MONTH` and `CALENDAR YEAR`, and `CALENDAR YEAR` reads the year to date, which is all that exists before the data year closes. `ROLLING` windows are not accepted.
### Time not captured
`TIME_NOT_CAPTURED` returns how long a period had no data for a data point, as a span of time rather than the fraction `DATA_CAPTURE_RATE` returns. It is the total time with no data in the period, whether or not the gaps are consecutive. Use it when a limit is written as time, such as "no more than 10 days of missing data a month".
```
TIME_NOT_CAPTURED({{data-point.ambient-temperature-c}} WITHIN CALENDAR MONTH) > 10.days
TIME_NOT_CAPTURED({{data-point.ambient-temperature-c}} WITHIN CALENDAR YEAR) >= 36.hours
```
It is compared with a **duration**: a whole number, a dot and a unit, such as `10.days`, `36.hours` or `1.week`. The units are seconds, minutes, hours, days and weeks, singular or plural. A plain number has no unit, so it is rejected, and a duration can only be compared with `TIME_NOT_CAPTURED`. It takes `WITHIN CALENDAR MONTH` or `WITHIN CALENDAR YEAR`, as `DATA_CAPTURE_RATE` does, and goes on the left of the comparison.
A slot holding a substituted value counts as not captured, the same as an empty one.
### When a capture rate rule is rejected
The data point type's **event type must declare a cadence**, since the cadence is what supplies the denominator. A rule referencing a data point type whose event type has no cadence will not save. See [Data capture rate](/data-collection/data-capture-rate) for declaring one.
Once a rule is saved, a period with no capture rate worked out yet reads as no value rather than as zero, and the comparison is false. A rule never fires merely because the figure is missing.
A rule is evaluated when data arrives, so this condition reports on a period once the stream delivers into it. A rule set to [run only on demand](/data-rules/create-a-rule#rules-that-run-on-demand) reports on a period when you evaluate it. To watch a stream that is quiet right now, and to see which slots are missing rather than how many, use the [Capture Rate](/data-collection/data-capture-rate) view.
These conditions are rejected when you save, and previewing rejects them too. Each applies to `TIME_NOT_CAPTURED` as well:
| If you | Why it is rejected |
| - | - |
| Use it on a `{{node.*}}` variable | Coverage is projected per data point type, so a node reference has nothing to read. |
| Use any other window | Only the two calendar windows above are stored. |
| Point it at a data point whose event type has no cadence | There is no denominator, so the data point type has no rate. |
| Point `TIME_NOT_CAPTURED` at a data point whose cadence is counted in months or years | A month or a year has no fixed length, so the gap has no span of time. Use `DATA_CAPTURE_RATE` for that data point. |
| Compare `TIME_NOT_CAPTURED` with anything other than a duration | It is a span of time, so a bare number such as `960` has no unit. |
| Put it on a substitute rule that runs automatically, or that corrects the data point whose rate it reads | A month's capture rate is final only once the month is complete, so an automatic rule would act on a partial figure. A substitute rule set to [run only on demand](/data-rules/create-a-rule#rules-that-run-on-demand) can read the capture rate of another data point, though not of the one it corrects. |
## Booleans and numbers
* Booleans are written `true` and `false` (case-insensitive).
* Numbers are written without quotes. Both integers and decimals work: `0`, `42`, `3.14`, `0.005`. Negative numbers are allowed.
* Durations are written as a whole number, a dot and a unit, such as `10.days`, and are compared only with [`TIME_NOT_CAPTURED`](#time-not-captured).
## Common patterns
These examples assume your project has data points with the slugs shown. Substitute slugs that exist in your project.
For these patterns as complete rules, grouped by the failure each one catches and carrying the Then-step settings, see [Commonly used rules](/data-rules/common-rules).
```
# Out-of-range alert
{{data-point.carbon_content}} not between 0.50 and 0.90
# Required value missing or blank
{{data-point.lab_sample_id}} IS MISSING OR {{data-point.lab_sample_id}} = ''
# Tracking ID format check
NOT {{data-point.tracking_id}} matches '[A-Z]{2}-[0-9]{6}'
# Batch CI sanity check
{{data-point.ci_score}} > 30 OR {{data-point.ci_score}} < -50
# Fewer than three lab samples recorded on one event
COUNT({{data-point.lab_sample_id}}) < 3
# More than one delivery logged in the same calendar month
COUNT({{data-point.delivery_mass}} WITHIN CALENDAR MONTH) > 1
# Repeated data points clustered within a week either side
COUNT({{data-point.meter_reading}} WITHIN ROLLING 7.days) > 3
# Flow meter stuck on one value for an hour, with at least three identical values behind it
MIN({{data-point.flow_rate}} WITHIN TRAILING 1.hour) = MAX({{data-point.flow_rate}} WITHIN TRAILING 1.hour) AND COUNT({{data-point.flow_rate}} WITHIN TRAILING 1.hour) >= 3
# Methane content more than two standard deviations from the month's average
ZSCORE({{data-point.methane_content}} WITHIN CALENDAR MONTH) NOT BETWEEN -2 AND 2
# Temperature far above what the last week of values would predict
ZSCORE({{data-point.temperature}} WITHIN TRAILING 7.days) > 3
# More product out than feedstock in, which no yield allows
{{data-point.biochar_mass}} > {{data-point.feedstock_mass}}
# Two meters that should agree drifting apart
RATIO({{data-point.inlet_flow}}, {{data-point.outlet_flow}}) NOT BETWEEN 0.95 AND 1.05
# This week's methane content below its own annual baseline
RATIO(AVG({{data-point.methane_content}} WITHIN ROLLING 7.days), AVG({{data-point.methane_content}} WITHIN CALENDAR YEAR)) < 0.85
```
## Tips for writing good rules
* **Phrase the rule as the failure case.** A rule with the expression `{{data-point.ph}} not between 6.0 and 8.0` triggers when pH is out of range, which is the alert you want.
* **Validate before saving.** Validating previews the matches from the last 90 days. Use it to catch over-eager rules before they generate noise. When nothing matches, a range filter appears with the result so you can look further back.
* **Cover every shape of an empty value.** `IS MISSING` catches a data point with no value stored. It misses a text data point holding an empty string and a boolean holding `false`, so a rule that has to catch those needs each test spelled out with its own variable: `{{data-point.lab_sample_id}} IS MISSING OR {{data-point.lab_sample_id}} = ''`.
* **Say why in the rule name.** Expressions are terse and a rule carries no separate description field, so the name and the alert message are where you explain what the rule is for.
# Customer Portal
Source: https://docs.mangrovesystems.com/inventory/customer-portal
Invite customers into a branded portal to share MRV data for their deliveries.
## Administering the Portal
#### Configure the Portal
Portal settings for customer portals are managed in the Admin > Portal section of Mangrove. Admin users can configure the domain name, branding, and redirect URLs of the portals.
* **Subdomain:** This is the URL that external users (partners or customers) would visit for the Portal. It can be configured at `Admin > Portal > Subdomain`
* **Logout Redirect URL:** When users log out of the Portal, they can be redirected to a URL that you specify.
* **Account Request URL:** Visitors to your portal subdomain may select `Request Account`. This would be the URL that they get redirected to - it defaults to Mangrove Systems' contact form, but you can set up an account request form URL here.
You can preview a customer's experience of the Portal by going to the "Users" section under that specific customer.
#### Managing Portal Users
Portal users are managed through each customer's "Users" section. You can add and edit portal users in this section.
Once added, portal users receive an email invite to create their credentials and log in at the configured portal domain.
# Inventory Fundamentals
Source: https://docs.mangrovesystems.com/inventory/fundamentals
Key credit inventory concepts used in Mangrove
#### Issuance
An issuance in Mangrove refers to a group of active, tradable credits within your inventory. These credits represent carbon production that has been demonstrated in a project within a specific production period.
#### Customer
A buyer (or potential buyer) of credits from your inventory. Customers make orders and receive deliveries.
#### Order
An order represents a request made by a customer for credit inventory. Each order contains details such as the customer's information, the order terms and status, and the deliveries included in the order.
```mermaid theme={null}
flowchart LR;
Customer --- Order;
Order --- Delivery1[Delivery 1];
Order --- Delivery2[Delivery 2];
Order --- Delivery3[Delivery 3];
```
#### Delivery
Deliveries are created within the Orders that Customers place. Each Order can hold multiple Deliveries, from various Projects. A delivery can be a transfer or a retirement.
```mermaid theme={null}
flowchart LR;
Order --- Delivery;
Delivery --- Issuance1[Issuance];
Delivery --- Issuance2[Issuance];
Delivery --- Issuance3[Issuance];
Issuance1[Issuance] --- Project1[Project];
Issuance2[Issuance] --- Project1[Project];
Issuance3[Issuance] --- Project2[Project];
```
# Getting started
Source: https://docs.mangrovesystems.com/inventory/getting-started
Take the following steps to set up your inventory of credits and environmental attributes.
You will need to add credits to each project's inventory, in order to fulfill customer orders from this inventory.
* Within the **Issuances** section of a project's **Credit Inventory**, create an Issuance or a Forecast
* **Create Issuance** adds a spot issuance to the project's inventory
* **Forecast Issuance** adds a single, or repeating, forecasts of credit issuances for future production periods
* Read more about issuances here
In the Customers section - add a new customer and their preferred currency.
* Orders recorded for this customer will have the currency set as the default
* Save contacts, and important information about the customer's registry accounts
* In the Orders section, use New Order to create an Order for the customer
* Add deliveries to the order to divide the order across a schedule of deliveries
* The Order Status of the order determines whether the credits committed are considered reserved/sold and therefore not available for other orders
# Track customer orders
Source: https://docs.mangrovesystems.com/inventory/orders
An order represents a request made by a customer for credit inventory. Each order contains details such as the customer's information, the order terms and status, and the deliveries included in the order.
## Orders
An Order represents a commitment from a Customer to purchase credit units from your projects. Mangrove allows you accurately track orders across all your customers to ensure you have one consolidated view of all the offers and commitments made from the credit inventories of your projects.
An Order serves as a container for one or more Deliveries (separate fulfillments of the order) to the customer. Orders can include deliveries from multiple projects, and do not require all deliveries to be made at once.
The total quantity of an order is the sum of the committed units across all included deliveries, while the total revenue of an order is the sum of the value of all included deliveries.
### Recording an order
Orders can be created in two ways:
* Through the **Orders** Section
* Navigate to a specific customer and initiate a new order within their view
Orders can only be created for existing customers. Ensure that a Customer already exists before creating a new order.
Record the details of the order. For more on specific settings, see the list below.
The status of the order determines whether the included credits are considered fully committed - statuses such as `Draft`, `Expired` or `Cancelled` do not consider their included credits as committed from the inventory. New order statuses can be configured by admins within the Account Admin.
You can choose to record the Retirement Beneficiary details upfront to ensure you can execute future transactions with those details when the credits are available for delivery.
This is a reference to an external identifier. Useful if customers are tracked in a separate system like a CRM. If Mangrove integration is turned on, this order reference is automatically populated.
Document any specific terms such as payment or delivery terms that need to be fulfilled as part of this order.
* In the delivery, **Add Delivery** for each delivery within the agreed upon delivery schedule. Set the estimated date, quantity of units from a specific project.
* Record the price of each delivery, to ensure that the total value of the order is fully tracked.
A project needs to have either existing issued credits, or forecasted issuances of sufficient quantity, for a delivery to be saved from it. If you are having issues creating Deliveries, ensure that the project has sufficient credits to fulfill the delivery.
Mangrove automatically fulfills this delivery with the best available credits from the selected project. However, you have the option to override this by manually selecting the credit issuances to fulfill the delivery with.
- Use **Manually Fulfill Delivery** to specify which credits should fulfill the delivery.
- Enter the quantity from each listed issuance or forecast that you'd like to use
- The total across these quantities is the amount of credits delivered to the customer
You can save documents like contracts and specific customer files specific to each order.
You have agreed to a three-year offtake deal with AcmeCorp, with a commitment to retire credits from multiple projects, twice a year. This order is created in Mangrove under the customer AcmeCorp, with a delivery preference of Retirement. 6 separate deliveries are then added to the order, distributed across the three selected projects.
### Deliveries
Once Deliveries have been created, they can be accessed:
* Within the specific Order
* Within the **Credit Deliveries** of each project
### Customer payments
A payment from a customer can be recorded on each delivery. Payments can be added, edited, or deleted on each delivery.
The Order's payment status summarizes payments across all the deliveries contained in the order. e.g. A `Partially Paid` order has both deliveries that have been paid, and deliveries that have not been paid.
# Unit prices and fees
Source: https://docs.mangrovesystems.com/inventory/prices-fees
The unit price of customer deliveries can be recorded in Mangrove, along with any expected fees due to value streams.
Our platform takes a consistent approach to rounding values across the platform in order to ensure that transaction values are rationalized across currency exchange rates as well as commission/fee percentages in our order management features.
Transaction values and fees are calculated with bankers' rounding, or rounding half to even. Values are rounded to the nearest cent. When a value lands at the midpoint between two cents (e.g. x.5 cents), the value is rounded to the nearest rounded value such that the least-significant digit is even. For example, 23.5c becomes 24. 24.5c also becomes 24.
Learn more about [**bankers' rounding**](http://en.wikipedia.org/wiki/Rounding#Round_half_to_even).
Here's an example to better illustrate why we do this type of rounding:
| **Original** | **Rounding to nearest** | **Bankers' rounding** |
| - | - | - |
| 54.1754 | 54.18 | 54.18 |
| + 343.2050 | + 343.21 | + 343.20 |
| + 106.2038 | + 106.20 | + 106.20 |
| **= 503.5842** | **= 503.59** | **= 503.58** |
When using the standard approach of rounding to the nearest, you round up or down to the nearest number. For amounts in the middle (ending in 5), it rounds up. This results in a bias towards a higher amount. Bankers' rounding is closer to the actual amount.
# Retire credits
Source: https://docs.mangrovesystems.com/inventory/retirements
Credit retirements can be recorded within Mangrove to keep your inventory up to date.
Within each Customer order, find the right delivery, and retire the credits to complete the delivery.
Puro CORCs can be retired directly through Mangrove through an integration with MyPuro.
Ensure that your Mangrove account has your API Key credentials configured. Speak to the Mangrove team if you are unsure whether this is configured!
* The Project's **External Registry Account Number** is set on **Project > Project Details > Registry Details** section
* The CORC Bundle number should be set on the issuance's **Registry External ID**
* If the credits are from a Puro project, a retirement form will collect the necessary details for Puro to accept the retirement
* Once the retirement is complete, the transaction ID will be visible on the credit Delivery
Think we should integrate with a registry? Reach out to Mangrove to discuss.
# Transfer credits
Source: https://docs.mangrovesystems.com/inventory/transfers
Credit transfers to your Customers can be recorded within Mangrove to keep your inventory up to date.
Within each Customer order, find the right delivery, and **Transfer** the credits.
Puro CORCs can be transferred directly through Mangrove to customer's Puro accounts through a Mangrove integration with MyPuro.
Ensure that your Mangrove account has your API Key credentials configured. Speak to the Mangrove team if you are unsure whether this is configured!
* The Project's **External Registry Account Number** is set on **Project > Project Details > Registry Details** section
* The CORC Bundle number should be set on the issuance's **Registry External ID**
* Request the Customer's Puro registry account number (they will have to get that from their MyPuro portal)
* Within the **Customer**, add their Puro registry account under the **Registry Accounts** section
* If the credits are from a Puro project, a transfer form will collect the necessary details to complete the transfer directly within Puro
* Select the Customer's **Registry Account** that the credits are to be transferred to
* Confirm any additional notes, the Unit Price and Currency of the credits being transferred, and add a **Beneficiary Contact Email** which will receive an email confirmation from Puro when the transfer has been completed.
* Once the transfer is complete, the transaction ID will be visible on the credit Delivery
CSI credits can currently only be transferred through the Global C-Sink Tool in the CSI EasyCERT portal. We are working with CSI on activating a more direct transfer mechanism.
Think we should integrate with a registry? Reach out to Mangrove to discuss.
# Mangrove Overview
Source: https://docs.mangrovesystems.com/overview
Your guide to using Mangrove.
## Best-In-Class dMRV
Mangrove is designed to address every step of the credit issuance cycle, with key dMRV building blocks of Production Accounting (monitoring, data collection, reporting) and Credit Inventory Management.
Collect your data and run accounting for monitoring, reporting, and verification (MRV)
Report your production to value streams to originate credits and environmental attributes.
Manage your credit inventory.
Mangrove Systems is excited to work with you to configure Mangrove for your project operations. We're committed to being a strong partner to our customers and look forward to supporting your important work.
# Portfolio Account Management
Source: https://docs.mangrovesystems.com/portfolio/portfolio-account-management
Provide partners with dedicated accounts and branded portals for streamlined MRV data management
Portfolio Account Management is available to customers with multi-partner portfolios. Contact Mangrove to enable partner account features and discuss branding customization options for your portfolio.
## Overview
Portfolio Account Management in Mangrove enables you to create dedicated, managed accounts for your project partners, giving them direct access to submit MRV data and view their project performance through a portfolio-branded portal experience. This feature streamlines partner collaboration while maintaining centralized oversight of all portfolio activities.
## Key Features
### Managed Partner Accounts
Create dedicated Mangrove accounts for each partner organization:
* **Unified Project Management**: All of a partner's projects and data are organized cleanly within their managed account
* **Streamlined Onboarding**: Partners receive their own login credentials and branded portal access
* **Centralized Data Organization**: Complete project portfolios managed under a single partner account
* **Account Hierarchy**: Maintain oversight while giving partners operational independence
### Direct MRV Data Submission
Empower partners with intuitive tools for data management:
* **Self-Service Data Entry**: Partners can directly submit production data, transport events, and evidence
* **Bulk Import Capabilities**: Upload large datasets using Mangrove's bulk import tools
* **Real-Time Validation**: Immediate feedback on data quality and completeness
* **Evidence Management**: Direct upload and organization of supporting documentation
### Comprehensive Project Visibility
Partners gain full transparency into their project performance:
* **MRV Dashboard**: Real-time visibility into monitoring, reporting, and verification status
* **Commercial Inventory Tracking**: Complete view of credit issuance, transfers, and retirements
* **Production Analytics**: Detailed reporting on project performance and credit generation
* **Compliance Status**: Clear indicators of verification requirements and regulatory compliance
### Portfolio-Branded Portal Experience
Partners access their data through your branded interface:
* **Custom Branding**: Portal reflects your organization's visual identity and branding
* **White-Label Experience**: Partners interact with your brand, not Mangrove directly
* **Consistent User Experience**: Fits directly into your existing partner communications
* **Custom Domain Options**: Optional custom URL structure for enhanced brand consistency
## How It Works
### Account Setup Process
1. **Partner Account Creation**: Set up a new managed account for your partner organization
2. **Project Assignment**: Assign existing or new projects to the partner account
3. **Permission Configuration**: Define what data and features the partner can access
4. **Branding Customization**: Apply your portfolio branding to the partner's portal experience
5. **User Onboarding**: Partner receives login credentials and access to their branded portal
### Partner Data Workflow
1. **Portal Access**: Partners log into their portfolio-branded Mangrove portal
2. **Project Selection**: Choose from their assigned projects for data submission
3. **Data Entry**: Submit production data, events, and evidence directly through the interface
4. **Real-Time Sync**: Data immediately appears in your central portfolio dashboard
5. **Performance Monitoring**: Partners track their project status and credit inventory
## Benefits for Portfolio Managers
**Centralized Oversight**
* Complete visibility across all partner accounts and projects
* Consolidated reporting and analytics across your entire portfolio
* Standardized data collection processes across all partners
**Streamlined Operations**
* Reduced manual data collection and entry
* Automated partner onboarding and account management
* Consistent data quality through built-in validation
**Enhanced Partner Relationships**
* Professional, branded experience builds trust and engagement
* Partners feel valued with dedicated portal access
* Improved data transparency strengthens collaboration
## Benefits for Partners
**Operational Efficiency**
* Direct access to submit and manage their own project data
* No more manual data transfers or email-based reporting
* Real-time project performance monitoring
**Complete Transparency**
* Full visibility into MRV status and commercial inventory
* Understanding of credit generation and sales performance
* Access to detailed analytics and reporting
**Professional Experience**
* Clean, branded interface that feels like part of your organization
* Intuitive tools designed for ease of use
* Responsive support through the portal
## Getting Started
### Creating a Managed Account
1. In the main navigation, click the accounts dropdown in the top left
2. Select **+ Add New** to create a new managed account
3. Complete the partner organization setup form
4. Configure account permissions and branding preferences
### Setting Up Projects
1. Within the new managed account, create a project for your partner
2. Configure the project's data inputs, locations, and accounting settings
3. Set up any required integrations or data sources
### Adding Partner Users
1. When ready to onboard your partner, navigate to the managed account settings
2. Add partner users by inviting them to the managed account
3. Configure user roles and permissions for each partner team member
4. Partners will receive login credentials and access to their branded portal
# Supplier Payments
Source: https://docs.mangrovesystems.com/portfolio/supplier-payments
Manage supplier payments
The features described here are only available to customers with Supplier Payment Management turned on. Talk to Mangrove about turning these features for your account.
## Paying a Supplier Partner for a Delivery
You can log a Supplier Payment once a buyer has paid for a delivery.
Within the specific delivery, the `Add Payment` button allows you to record a buyer payment. When you log the buyer payment, you can also add details around the supplier payment currency, FX rate, and amount.
### Setting Supplier Payment Date
1. In the Order, open a specific Delivery
2. `Edit` the Delivery
3. Set the `Estimated Supplier Payment Date` for this Delivery
This is the payment date that is reflected to the supplier partner on statements and portal views.
### Setting the Supplier Payment Amount
You can set the amount paid to a supplier Partner alongside the Customer Payment. This amount requires the supplier Partner's preferred currency, as well as either the exchange rate or the actual amount paid to the supplier partner.
1. In the Delivery, `Edit` the `Payment`
2. The `Supplier Payment Currency` reflects the preferred currency of the supplier partner
3. Enter the `FX Rate` when the supplier partner was paid, or switch to `Input supplier payment` and enter the absolute amount in the supplier payment currency
Once saved, this is the amount that is reflected to the supplier partner on statements and portal views.
# Create a new project
Source: https://docs.mangrovesystems.com/production-accounting/add-project
Projects in Mangrove are managed in the "Projects" section.
## Creating a Project
You can create a new Project in the "Projects" section.
Creating a new Project:
* **Fill in Project Details**: To create a new project, users are required to input various details, including:
* **Project Name**
* **Description**
* **Type**: The technology/pathway being applied by the project
* **Project Status**: The current status of the project. Only `Active` projects will have available inventory that can be sold and delivered to Customers.
* **Registry Details**: Additional details such as registry IDs and the fee percentage can also be added.
You can also create a new Project by duplicating an existing one:
1. Go to the `Projects > All Projects` section
2. On the template project, select the `...` to the right and select `Duplicate Project` from that dropdown
3. Go to the copied project > `Project Details`
4. `Edit` and rename and edit the details of that project.
## Managing a Project
Once a Project has been created, you can easily access that specific Project from the side navigation. Each Project provides a single workspace to manage that project's production data, commercial inventory, as well as additional details and assets.
### Managing Production Data
If production data collection has been activated on a project, you will see the following tabs: `Data Inputs` , `Production Models` , and `Production Inventory` .
Within each Project, the ongoing production data can be managed by [collecting data](/production-accounting/data-inputs), running them through calculations in the accounting models, and tracking the outputs in **Production Accounting**. The batches of production can then be allocated from the production inventory, to commercial Issuances.
### Managing Commercial Inventory
WIthin each Project, the inventory of commercial credits can be tracked in the **Credit Inventory**. The credits for each Project are tracked as Issuances, then delivered to Customers.
# Manage project assets
Source: https://docs.mangrovesystems.com/production-accounting/assets
Track the physical equipment behind your production data (chargers, meters, wells, kilns, vehicles) as first-class assets.
Assets represent the physical equipment that generates or measures your production data. Each asset can belong to a location, to a project directly, or both, and carries its own typed metadata through [custom fields](/account-admin/custom-fields).
Manage a project's assets in **Settings → Assets**. From there you can create assets, edit their details and custom field values, export the asset list, and edit many assets at once by importing a spreadsheet.
### What an asset carries
* **Name**: how the asset appears across the platform.
* **Location and project**: an asset can be parented to a location (a site or facility), attached to a project, or both.
* **Coordinates**: per-asset latitude and longitude, independent of the parent location.
* **Custom field values**: typed attributes defined by your account's asset custom fields, such as a serial number, registration status, or registry-assigned ID. See [Custom Fields](/account-admin/custom-fields).
### Editing assets in bulk
To change many assets at once, such as setting a registration status across a fleet of chargers, export the asset list, edit the file, and import it back. Bulk editing is enabled per account; ask your Mangrove account team if you do not see **Import** on the Assets page.
1. Click **Import**, then **Export assets** to download the current list as CSV or Excel. Start from this file so the Asset IDs match.
2. Edit the file and upload it. Only the columns in your file are edited. An empty cell empties the saved field, except on a required field, where the row cannot be imported. Rows that do not match an existing Asset ID are skipped.
3. Review the preview before anything is saved. It shows each asset that will be edited, with its current value beside the value after import, plus the rows that cannot be imported, the rows with no matching asset, and the assets that already match the file.
4. Click the button under the preview, such as **Import 12 assets**, and confirm. You can close the window while it runs.
A file can hold up to 5,000 rows, and one import edits up to 1,000 assets. Rows past that are left alone, so move them into a second file. An asset edited by someone else after you uploaded the file is skipped rather than overwritten. A preview you do not apply expires after seven days.
### Import history and reverting an asset import
**Import history** on the Assets page lists every import on the project, with who uploaded it, when, and its result. Open one to see what it edited, download the file it used, or revert it.
**Revert import** puts each edited asset back to the value it had before the import. A preview comes first and lists any asset that will not be reverted, with the reason: it has been edited since the import, it is no longer in the project, reverting would empty a required field, or one of its fields cannot be read. An import with some assets left in place reads **Partly reverted**, and **Finish reverting** tries those assets again. To apply the edits again after reverting, import the file again.
### Attaching events to assets
Events can be linked directly to the asset that produced them. When an event type has **Requires asset** enabled, every event of that type must reference an asset, so per-equipment data can't lose its linkage during ingestion.
Data feeds attach each event to its asset by the asset's friendly ID. Incoming rows must carry that ID for the event to be linked.
### Per-asset batches
Models can partition batches by asset, producing one batch per piece of equipment in the generation window. This is the natural fit for programs that report per-unit (per-charger, per-meter, per-well), and each batch surfaces the asset it belongs to.
### API access
Assets are available through the API. See [List assets](/api-reference/assets/get-all) and related endpoints. Asset custom field values are readable and writable via the same endpoints, which lets external systems keep registration state in sync.
# Configure data inputs
Source: https://docs.mangrovesystems.com/production-accounting/data-inputs
Data inputs feed into the production models of Mangrove, driving the calculations that are fundamental to the calculation of each project's production.
You can view the ongoing data collected within a project in the `Data Inputs` section of the project.
#### Types of Data Inputs
There are three primary types of data inputs in Mangrove:
* **Events:** Events are inputs that occur with high frequency. Each event is a set of measurements (Datapoints) that represent operational activity that is dynamic and regularly changing from batch to batch in a project. Examples:
7 datapoints:
* `Hourly Mass Injected (t)`
* `Mass Flow Rate (t/s)`
* `Mass Totalyzer (t)`
* `Injectate temperature (°C)`
* `Injectate density (kg/L)`
* `Meter status`
* `Meter last calibration date`
4 datapoints:
* `Wet Mass Transported (kg)`
* `Loaded Distance Transported (km)`
* `Deadhead Distance Transported (km)`
* `Vehicle Type`
* **Static Inputs:** Static inputs hold datapoints that remain constant over an extended period. They are used for the calculations across multiple production batches within a project. Example: **`% of Facility Electricity Usage Related to Carbon Production`**
- 0.00043 t/kW\.h
- Relevant date range: 2025-01-01 - 2025-12-31
* 0.00045 t/lb
* **Resource Library Inputs**: Resource library inputs are a variant of static input, drawn from Resource Libraries available in Mangrove. These Resource Libraries are sourced from scientific databases and provide shared variables that are used in production and quantification model calculations. Examples: **`Global Warming Potentials of greenhouse gases`**
#### Configuring Event Types
Events Types have to first be configured for the Project, before you can collected data for them.
* **Configuring Event Type**: Add or modify ongoing types of events that can be collected per project. This can be done in the `Data Inputs > Input Settings` section of each Project.
#### Configuring Static Inputs
* Add or adjust the values of static inputs. This can be done in the `Data Inputs > Static Inputs` section of each Project.
* Ensure Static Inputs are used in the accounting dataflow
In order for inputs to be considered in model calculations, the production models need to have input nodes that reference the data from these event types or static inputs. See [building accounting models](/production-accounting/models), and please reach out to Mangrove Systems if you need help configuring your model.
#### Uncertainty
You can set uncertainty on specific inputs to capture and report the uncertainty in your batch calculations. Read more on uncertainty propagation [here](/production-accounting/uncertainty-propagation)
# Embodied Emissions
Source: https://docs.mangrovesystems.com/production-accounting/embodied-emissions
Track and amortize the carbon cost of facilities and equipment across production batches.
Embodied emissions represent the carbon cost of building and installing the physical infrastructure that makes a project possible. Mangrove tracks these emissions in a dedicated system ledger and automatically spreads them across production batches using configurable amortization rules.
## What are embodied emissions?
Carbon removal and renewable fuel projects must account for **all** emissions within their lifecycle boundary, not just the operational ones. Embodied emissions capture the carbon footprint of manufacturing equipment, constructing facilities, and other major capital expenditures that enable a project to operate.
Unlike process emissions (energy consumed during production) or upstream emissions (feedstock transport), embodied emissions are typically **large, one-time costs**. A pyrolysis reactor has a manufacturing footprint. A biochar facility has a construction footprint. These costs need to be fairly distributed across the project's total output rather than assigned to a single batch.
Carbon accounting methodologies typically require projects to include embodied emissions in their lifecycle assessment (LCA). Mangrove's system ledger approach automates this allocation.
For simple projects, embodied emissions can be modeled as static inputs subtracted directly in the model (see [LCA & Emissions](/accounting-academy/module-3/3-3-lca-and-emissions)). For projects needing balance tracking, automatic amortization, and audit trails, use the system ledger approach described below.
## How it works in Mangrove
Mangrove creates a dedicated **Embodied Emissions ledger** (unit: kgCO₂e) for each project. You add individual embodied emission entries to this ledger, each with a name, total emission amount, and an amortization rule that controls how the cost is spread across batches.
When you generate batches, the production model automatically calculates the amortized deduction for that batch period. The ledger tracks how much has been amortized to date and how much remains.
```mermaid theme={null}
flowchart LR
EE["Embodied Emission
(total kgCO₂e)"] --> RULE["Amortization
Rule"]
RULE --> BATCH["Per-Batch
Deduction"]
BATCH --> NET["Net Carbon
Output"]
GROSS["Gross Carbon
Removed"] --> NET
style EE fill:#ffa726,stroke:#f57c00,stroke-width:2px,color:#000
style RULE fill:#42a5f5,stroke:#1976d2,stroke-width:2px,color:#000
style BATCH fill:#ffa726,stroke:#f57c00,stroke-width:2px,color:#000
style GROSS fill:#66bb6a,stroke:#388e3c,stroke-width:2px,color:#000
style NET fill:#ef5350,stroke:#c62828,stroke-width:2px,color:#000
```
## Amortization rules
Each embodied emission uses one of three amortization rules to determine how the total cost is spread across batches.
### Project Lifetime
Spreads emissions **evenly over time** based on the project's expected lifespan.
The deduction for each batch is proportional to the batch's time range relative to the total project lifetime.
> **Example:** 1,000 kgCO₂e amortized over 10 years. A 30-day batch deducts approximately 8.2 kgCO₂e.
**Best for:** Facility construction, equipment with a known useful life, infrastructure with time-based depreciation.
### Project Production
Spreads emissions **proportionally to actual output**. Batches that produce more carbon removal bear a larger share of the embodied cost.
> **Example:** 1,000 kgCO₂e spread over 50,000 tCO₂e of expected total production. A batch producing 500 tCO₂e deducts 10 kgCO₂e.
**Best for:** Equipment where the carbon cost is tied to total output capacity (e.g., a reactor rated for a certain lifetime throughput).
Project Production amortization requires a **reference node** configured in the production model. This is the gross production output that determines proportional allocation. Without it, batch generation will fail.
### No Amortization
Tracks the emission in the ledger **without automated batch deduction**. The total amount is recorded but not spread across batches.
**Best for:** Emissions handled manually in the model, or tracked for audit and record-keeping purposes only.
Once the total amount has been fully amortized (for Project Lifetime or Project Production rules), subsequent batches receive zero deduction. The system prevents over-amortization and will never deduct more than the original total.
## Add an embodied emission
Navigate to your project's ledger list and open the **Embodied Emissions** ledger.
Click **Add** to open the creation form.
Provide a **Name** (e.g., "Facility Construction" or "Reactor Manufacturing") and the **Total Emissions** value in kgCO₂e.
Choose an **Amortization Rule** and fill in the required parameters:
* **Project Lifetime:** Enter the **Est. Project Lifetime** in years
* **Project Production:** Enter the **Est. Project Gross Tonnage**
* **No Amortization:** No additional parameters needed
Optionally add a **Description** and attach **evidence** such as invoices, manufacturer specs, or URLs that support the emission value.
Click **Add**. Mangrove creates the emission entry and runs the initial model calculation.
## Connect to your production model
Adding an embodied emission to the ledger is only the first step. To include it in batch calculations, you need to wire it into the production model.
Navigate to your project's model and open the **Model Editor**.
Add an `emission_amortization` operator node to the model tree.
Set the node's `embodied_emission_slug` to match the slug of the embodied emission you created. You can find the slug in the embodied emission detail drawer.
If using the **Project Production** rule, set the `reference_node_slug` to point to your gross production output node. This tells the system which output to use for proportional allocation.
Connect the amortization node's output to your net carbon calculation, typically as a deduction from gross carbon removed.
**Generate a test batch** to verify the amortized amount appears correctly in the batch output.
## Evidence
Each embodied emission can have supporting evidence attached, including files or URLs that document the source of the emission value. This creates an audit trail from reported batch values back to the original data.
Common evidence includes:
* Manufacturer lifecycle assessment reports
* Construction invoices and receipts
* Equipment specification sheets
* Third-party LCA studies
Evidence can be attached when creating the emission or added later through the detail drawer.
# Feedstock Accounting
Source: https://docs.mangrovesystems.com/production-accounting/feedstocks
Track different input materials used in your production processes and perform production accounting on a per-feedstock basis. This allows you to split production into unique feedstock-specific batches, providing granular visibility into how different input materials contribute to your outputs and carbon metrics.
The Feedstocks feature requires activation. Please contact your Mangrove representative to enable this feature for your project.
**Common use cases:**
* RNG projects tracking different manure types (dairy, swine, poultry)
* Biochar projects using multiple biomass sources (corn stover, wood chips, agricultural residues)
* Biofuel facilities processing various waste streams
* Any project needing to differentiate production by input material type
## Tracking Feedstocks
### Creating a Feedstock
1. Navigate to **Data Inputs > Feedstocks**
2. Click **Create Feedstock**
3. Enter a descriptive name (e.g., "Dairy Manure - Smith Farm")
4. Select a Feedstock Type from the available options
5. Click **Create**
Your new feedstock will appear in the table and is now available for use when creating events.
### Associating Events with Feedstocks
When creating or editing an event in **Data Inputs > Events**, select the appropriate feedstock from the **Feedstock** dropdown. This tags the event data with that feedstock for use in production accounting.
## Production Accounting by Feedstock
When configured for feedstock partitioning, Production Accounting generates a separate batch for each feedstock per accounting period. This splits your production into unique feedstock-specific batches.
**Example:** A project with two feedstocks (Corn and Wheat) running monthly accounting will generate two batches per month, one for each feedstock, with separate calculations and carbon metrics.
### Viewing Feedstock Batches
1. Go to **Production Accounting** and select your ledger
2. The **Feedstock** column shows which feedstock each batch represents
3. Use filters to view batches for specific feedstocks
4. Click a batch to see detailed production calculations and the associated feedstock in the header
# Production Accounting Fundamentals
Source: https://docs.mangrovesystems.com/production-accounting/fundamentals
Key production accounting concepts used in Mangrove
#### Project
A **Project** defines a set of activities that, as a whole, produces net climate benefits relative to a baseline scenario. All data inputs, accounting, reports and credit inventory live within a project. You can [create a project](/production-accounting/getting-started) and configure its models and data inputs.
#### Event
**Events** are operational activities within a project's boundaries. Each event holds one or more datapoints and represents something that happened at a point in time (e.g., a delivery, a meter reading, a production run). Events are one of the main types of data inputs. They occur with high frequency and their measurements often change from batch to batch. They can be associated with locations, feedstocks, evidence, and other attributes.
#### Datapoint
A **datapoint** is a single measured value used in production accounting. Events contain datapoints (e.g., mass, flow rate, concentration). Models consume datapoints as inputs and can output new datapoints.
#### Evidence
**Evidence** is documentation attached to datapoints (delivery tickets, weighbridge slips, meter readings, etc.). Evidence supports the audit trail from reporting back to source data.
#### Feedstock
A feedstock is the raw material used in the production process. In Mangrove, feedstocks are associated to the incoming Events for use in accounting and reporting.
#### Batch
A **batch** is the main unit of production in a project. Each batch contains the calculations and MRV data supporting that batch's gross and net carbon activity. Batches are linked to all source data inputs and underlying evidence, and are [generated](/production-accounting/generate-batches) by running the accounting dataflow over a chosen period.
#### Score
A **score** is an output calculation representing the carbon intensity (CI) of a reporting period. Scores are backed by a copy of the executed model.
#### Model
A **model** is a set of calculations that produce production accounting. Two main types exist: **production models** (operational calculations) and **quantification models** (methodology-specific carbon accounting). Models are built in the Model Editor using a tree of nodes: inputs, intermediate calculations, and outputs. Read more about [production models](/production-accounting/models).
#### Value Stream
A value stream is a way for a project to originate and commercialize its environmental attributes.
#### Ledger
**Ledgers** (mass accounts) track material or carbon flows across stages of a project. [Mass balance accounting](/production-accounting/mass-balance-accounting) uses ledgers to record accumulation and use of material at each stage (e.g., feedstock received, biochar produced, carbon delivered). Not every project needs multiple ledgers. A single ledger is often enough for simpler flows.
#### System Ledger
A **system ledger** is a ledger auto-created by the platform to track non-physical quantities like [embodied emissions](/production-accounting/embodied-emissions) or renewable [instruments](/production-accounting/instruments). Unlike user-created mass accounts, system ledgers are managed automatically and have specialized behavior for amortization and inventory tracking.
#### Embodied Emission
An **embodied emission** is the carbon cost of facilities, equipment, and construction, tracked in a [system ledger](/production-accounting/embodied-emissions) and amortized across batches over time or production. Embodied emissions ensure that one-time capital carbon costs are fairly distributed across a project's output.
#### Instrument
An **instrument** is a renewable energy certificate (REC) or renewable fuel certificate tracked in a [system ledger](/production-accounting/instruments). Instruments represent an inventory of environmental attributes that gets consumed as it is allocated to production batches, with balance tracking to prevent double-counting.
# Generate batches
Source: https://docs.mangrovesystems.com/production-accounting/generate-batches
Calculate the ongoing production outputs using collected data.
The **Production Accounting** section of a project tracks the ongoing outputs in the form of production batches. Data inputs that have been collected are used to calculate the production output.
Batches on the Production Accounting ledger are generated through an accounting dataflow.
* `Generate Batches`
* Validate that all necessary datapoints exist within your desired accounting period
Pro tip: if validations on your selected date range are showing missing datapoints that you know exist, try setting a specific datetime range using the `show time` feature
* `Submit` the form, this runs the dataflow which you can see in the `Models` section of the Project.
Batch generation runs in the background. To view the status of current and past jobs, click the **⋯** menu next to `Generate Batches` and select **Batch generation jobs**.
A data point with a rule-proposed correction waiting for a decision cannot go into a new batch. The form lists those inputs on a **Needs approval** tab, and **Generate** stops until each one is approved or rejected. See [Approve corrections](/data-rules/approve-corrections).
By default, batch generation uses the **active** version of each ledger's model. To generate batches against a different version (for example, to preview how a draft will calculate before activating it), override the model version in the batch generation form. See [Build accounting models](/production-accounting/models#model-versioning) for details.
# Generate CI scores
Source: https://docs.mangrovesystems.com/production-accounting/generate-scores
Calculate CI scores from actual and forecast production data from a reporting period.
### Step 1: Generate Production Batches
CI scores are calculated from **batches** on your actual and forecast ledgers. Start by generating those batches for the reporting period:
1. **Actual ledger**: Generate batches for the reporting period in **Production Accounting → \[Actual Ledger Name]** (e.g. "Gas Injected")
2. **Forecast ledger**: If configured, generate batches for the reporting period in **Production Accounting → \[Forecast Ledger Name]** (e.g. "Forecast Gas Injected")
Without batches in the reporting period, the Generate Score flow will not have any operational data to calculate CI off of.
### Step 2: Generate CI Score
1. Open **Production Accounting → CI Scores**
2. Click **Generate Score**
3. Select:
* **Model(s)**: one or more score models
* **Reporting period**
* **Date range**: this gets filled automatically from the reporting period, but user is able to adjust
* **Batches**: Production Batches get auto-detected within the range, and user can select actual and/or forecast batches to include
* **Inputs**: the grid lists each operational input the batches you selected supply, with its **Value** and **Source Batch**, so you can check them before generating. The grid shows what your batch selection supplies, and the score works its own inputs out when it runs, so the two can differ.
4. Click **Generate Score** to create the score. The models run in the background, and adds a new score at the top of the ledger.
5. The latest calculated scores are shown in the project' overview.
# Getting Started
Source: https://docs.mangrovesystems.com/production-accounting/getting-started
Take the following steps to set up your project for MRV and production accounting in Mangrove.
You can create a new Project in the "Projects" section.
* Fill in Project Details: To create a new project, users are required to input various details, including:
* **Project Name**
* **Description**
* **Type**: The technology/pathway being applied by the project
* **Project Status**: The current status of the project. Only `Active` projects will have available inventory that can be sold and delivered to Customers.
* **Registry Details**: Additional details such as registry IDs and the fee percentage can also be added.
You can also create a new Project by duplicating an existing one.
See [this guide](/production-accounting/data-inputs) for more details.
See [this guide](/production-accounting/locations) for more details.
# Instruments
Source: https://docs.mangrovesystems.com/production-accounting/instruments
Track renewable energy certificates (RECs) and renewable fuel certificates as inventory, and allocate them to production batches.
Instruments represent environmental attribute certificates, such as renewable energy credits (RECs) or renewable fuel certificates, that a project can claim against its emissions. Mangrove tracks these as inventory in dedicated system ledgers, with balance and consumption tracking to prevent double-counting.
## What are environmental instruments?
In carbon accounting, a **book-and-claim** model allows projects to purchase environmental certificates and apply them to reduce reported emissions, even when the renewable energy or fuel was not physically delivered to the project site.
**Renewable Energy Certificates (RECs)** represent proof that electricity was generated from a renewable source. A project that purchases RECs can claim its grid electricity consumption as renewable, reducing the grid emission factor applied in its LCA calculations.
**Renewable fuel certificates** represent the environmental benefit of using renewable fuel (such as biomethane or renewable diesel) instead of fossil fuel. These certificates track the carbon intensity reduction from the fuel substitution.
Both types must be tracked carefully to prevent **double-counting**. The same certificate cannot be claimed by multiple batches or projects. Mangrove's instrument ledgers enforce this through balance tracking and consumption accounting.
## Instrument types
Mangrove supports two instrument types, each tracked in its own system ledger:
| | Renewable Energy Credits (RECs) | Renewable Fuel (RF) |
| - | - | - |
| **Unit** | kW\.h | kgCO₂e |
| **Use case** | Offset grid electricity emissions | Claim renewable fuel environmental benefit |
| **Required fields** | Amount, External ID, Geography | Amount, External ID |
| **Optional fields** | Generation tech (wind/solar/other), issuance date, purchase date | Mass of fuel (t), energy intensity (gCO₂e/MJ), lower heating value (MJ/kg) |
| **Validity dates** | Valid from / valid to | Valid from / valid to |
## How it works in Mangrove
Each project can have up to two instrument ledgers: one for RECs and one for Renewable Fuel. Each instrument entry represents a purchased certificate with an amount and a unique external identifier.
As instruments are allocated to production batches, the **consumed amount** increases and the **balance** decreases. The ledger prevents over-consumption: you cannot allocate more than the remaining balance.
```mermaid theme={null}
flowchart LR
CERT["Purchase
Certificate"] --> LEDGER["Add to
Instrument Ledger"]
LEDGER --> ALLOC["Allocate to
Batches"]
ALLOC --> BAL["Balance
Decreases"]
style CERT fill:#42a5f5,stroke:#1976d2,stroke-width:2px,color:#000
style LEDGER fill:#66bb6a,stroke:#388e3c,stroke-width:2px,color:#000
style ALLOC fill:#ffa726,stroke:#f57c00,stroke-width:2px,color:#000
style BAL fill:#ef5350,stroke:#c62828,stroke-width:2px,color:#000
```
## Add an instrument
Navigate to your project's ledger list and open either the **REC** or **Renewable Fuel BCU** ledger.
Click **Add** to open the creation form.
Provide the certificate's **External ID**, the unique identifier from the issuing registry. This must be globally unique across all accounts.
Enter the certificate **Amount** in the appropriate unit: kW\.h for RECs, kgCO₂e for Renewable Fuel.
* **RECs:** Enter the **Geography** (required) and optionally the **Generation Technology** (Wind, Solar, or Other), **Issuance Date**, and **Purchase Date**.
* **Renewable Fuel:** Optionally enter **Mass of Fuel** (t), **Energy Intensity** (gCO₂e/MJ), and **Lower Heating Value** (MJ/kg).
Set **Valid From** and **Valid To** dates if applicable, and attach **evidence** such as certificate documents, purchase receipts, or registry URLs.
Click **Add**. Mangrove creates the instrument entry and runs the initial model calculation.
The external ID must be globally unique because it represents a specific certificate. If you get a uniqueness error, verify the certificate hasn't already been registered in another project or account.
## Track balance and consumption
Each instrument shows three key metrics in the ledger view:
* **Amount:** The original certificate value as entered
* **Consumed to date:** The total amount that has been allocated to production batches
* **Balance remaining:** What is left for future allocation
These metrics update automatically as batches are generated and instruments are consumed.
An instrument's amount cannot be reduced below its consumed-to-date value. If you need to correct an amount, you may need to regenerate affected batches first. An instrument with any allocations cannot be deleted. Remove the allocations before deleting the instrument.
## Connect to your production model
To include instrument deductions in batch calculations, wire the instrument ledger into the production model.
Navigate to your project's model and open the **Model Editor**.
Add a node that references the instrument ledger's output data point type.
Connect this node to your emissions calculation:
* **RECs:** Typically deducted from grid electricity emissions (reducing the effective grid emission factor)
* **Renewable Fuel:** Typically deducted from fuel-related emissions
**Generate a test batch** to verify the instrument deduction appears correctly in the batch output and the instrument balance decreases.
## Evidence
Each instrument can have supporting evidence attached, including files or URLs that document the certificate's provenance. This creates an audit trail from reported batch values back to the original certificates.
Common evidence includes:
* Registry certificate documents
* Purchase receipts or invoices
* Transfer documentation
* Registry URLs linking to the certificate record
Evidence can be attached when creating the instrument or added later through the detail drawer.
# Configure project locations
Source: https://docs.mangrovesystems.com/production-accounting/locations
Track locations within your project boundary where activity is occurring.
Locations can be added within the **Project Boundary** of each project. A primary location can be designated.
Each location can parent multiple [assets](/production-accounting/assets), the equipment or devices found at that site. Assets can also exist without a parent location when they're attached to the project directly.
# Mass Balance Accounting
Source: https://docs.mangrovesystems.com/production-accounting/mass-balance-accounting
Track material flows and their associated MRV calculations across various stages of your project.
Mass balance accounting in Mangrove enables projects to track material flows (feedstocks, masses processed, masses produced, or sequestrations) throughout various stages of production. This allows project developers to:
* Get a clear, unified view of material flows across the entire project.
* Maintain traceable records of every batch.
* Calculate, and carry forward, the carbon profile of individual batches at different production stages.
* Support auditing, and reporting needs with accurate, transparent data.
Each project can define multiple mass accounts to track the outputs at different stages. These mass accounts serve as ongoing ledgers that record the accumulation and usage of material at that stage. Each account can also be thought of as representing a source, stock, or sink in the process.
```mermaid theme={null}
graph LR
A1[Mass Account
stage] --> B[Mass Account
stage]
A2[Mass Account
stage] --> B
B --> C[Mass Account
stage]
```
Mass balance accounts need to be configured by a member of the Mangrove team - please reach out to get accounts set up.
#### Examples of accounts:
```mermaid theme={null}
graph LR
A[Biomass Received
capture stage] --> B[Biochar Produced
production stage]
B --> C[Biochar Delivered and Applied
sequestration stage]
```
```mermaid theme={null}
graph LR
A[CO2 Captured
capture stage] --> B1[CO2 Sequestered
sequestration stage]
A --> B2[CO2 Utilized
utilization stage]
A --> B3[CO2 Loss
loss stage]
```
```mermaid theme={null}
graph LR
A[Dairy manure in
feedstock stage] --> B[Biogas produced
production stage]
B --> C[Biomethane upgraded
production stage]
C --> D1[Biomethane injected
transmission stage]
C --> D2[Biomethane loss
loss stage]
```
# Model Operators
Source: https://docs.mangrovesystems.com/production-accounting/model-expressions
Write mathematical and logical operators in Mangrove's models.
## Standard Mangrove operators
### `map`
Use `map` as a lookup node in nexus\_nodes\_attributes: the child gives the lookup key, and the map node resolves that key through a resource parameter table.
```yaml Syntax theme={null}
- name: Some mapped value
operator: map
nexus_resources_library: My Library
nexus_resources_parameter: My Parameter
nexus_nodes_attributes:
- name: Lookup key input
data_point_type: some-key-slug
```
```yaml Biochar example theme={null}
- name: c_hc regression coefficient
operator: map
nexus_resources_library: Biochar permanence regression coefficients
nexus_resources_parameter: c
nexus_nodes_attributes:
- name: Biochar end use soil temperature
data_point_type: soil-temperature
```
## Complex expressions
Mangrove uses the [Keisan](https://github.com/project-eutopia/keisan) expression language to evaluate mathematical and logical expressions within model nodes. This allows you to write complex formulas directly in the `operator` field of a node.
### Basic Syntax
Expressions are written as strings in the `operator` field of a calculated node. Child node values are referenced by their slugified names (lowercase, underscores instead of spaces).
```yaml theme={null}
- name: Calculated Value
operator: "child_a * child_b + 100"
nexus_nodes_attributes:
- name: Child A
data_point_type: input-a-slug
- name: Child B
data_point_type: input-b-slug
```
### Notable Operators
The equations support these operators:
* Arithmetic operators
* Comparison operators
* Logical operators (e.g. `&&`)
* Built-in mathematical functions (e.g. the below list)
* **`abs(x)`**: Absolute value\
Example: `abs(-5)` → `5`
* **`sqrt(x)`**: Square root\
Example: `sqrt(16)` → `4`
* **`exp(x)`**: Exponential (e^x)\
Example: `exp(1)` → `2.718...`
* **`log(x)`**: Natural logarithm\
Example: `log(10)` → `2.302...`
* **`sin(x)`, `cos(x)`, `tan(x)`**: Trigonometric functions\
Example: `sin(0)` → `0`
* **`round(x)`**: Rounds a value. Use `round(x)` for nearest integer or `round(x, n)` for n decimal places.\
Example: `round(10/2, 2)` → `5.00`
* Conditional logic
Example: `if(condition, value_if_true, value_if_false)`
* List functions that work with arrays of values
* **`max(list)`**: Maximum value in a list
* **`min(list)`**: Minimum value in a list
* **`range(n)`**: Generate list `[0, 1, ..., n-1]`
* **`range(start, end)`**: Generate list from start to end-1
* **`round`**: Rounds a value. Use `round(x)` for nearest integer or `round(x, n)` for n decimal places.
* **`map`**: Maps a value from a linked library
* **`duration`**: Converts the time range of the incoming datapoint into a duration
* **`data_point_proportion`**: Extracts the allocation proportion from an allocated node
### When to Use Mangrove Operators vs Keisan Expressions
| Use Mangrove operators when... | Use Keisan expressions when... |
| :- | :- |
| Simple arithmetic on all children | Complex multi-step formulas |
| Standard aggregations (sum, avg, max) | Conditional logic (if/else) |
| Clearer, more readable models | Custom rounding or transformations |
## Best Practices
1. **Keep expressions simple**: Break complex calculations into multiple nodes rather than one long expression. This improves readability and makes debugging easier.
2. **Use explicit unit conversions**: Create separate nodes for unit conversions rather than embedding them in expressions.
3. **Name child nodes clearly**: Since expressions reference child nodes by slugified name, use descriptive names that make the expression readable.
4. **Test your expressions**: Use the Model Editor's test functionality to verify calculations with sample inputs before saving.
## Further Reading
* [Keisan Documentation](https://github.com/project-eutopia/keisan): Complete reference for the Keisan expression language
# Build accounting models
Source: https://docs.mangrovesystems.com/production-accounting/models
A model represents a discrete set of calculations and data associations to generate production accounting. There are 2 main types of models in Mangrove: production models, and quantification models.
## Building models
Models can be edited by Admin users in the Model Editor using a YAML-based scripting language.
* Open the **Model Editor** by clicking on **Edit/Test** besides the model name
The YAML script constructs carbon accounting models using a tree of interconnected nodes, where each node performs a calculation or holds a value. These nodes are organized in a hierarchical structure.
```mermaid theme={null}
graph LR
B1[Intermediate Calc A
operator: #123;#123; #125;#125; ] --> A[Output Node
operator: #123;#123; #125;#125;]
B2[Intermediate Calc B
operator: #123;#123; #125;#125;] --> A
C1[Data Input 1
datapoints] --> B1
C2[Input Node 2
constant] --> B1
C3[Input Node 3
static input] --> B2
C4[Input Node 4
datapoints] --> B2
style A fill:#57BECE,stroke:#333,stroke-width:2px,color:black
style B1 fill:#98A2B3,stroke:#333,stroke-width:1.5px,color:black
style B2 fill:#98A2B3,stroke:#333,stroke-width:1.5px,color:black
style C1 fill:#F9FAFB,color:black
style C2 fill:#F9FAFB,color:black
style C3 fill:#F9FAFB,color:black
style C4 fill:#F9FAFB,color:black
B1:::opA
B2:::opB
classDef opA fill:#e0f7fa,stroke:#00796b,stroke-width:1px,color:black;
classDef opB fill:#e0f7fa,stroke:#00796b,stroke-width:1px,color:black;
```
```yaml Example Model [expandable] theme={null}
nexus_nodes_attributes:
- name: Output Node
operator: summation
data_point_type: output-slug
nexus_nodes_attributes:
- name: Intermediate Calc A
operator: summation
nexus_nodes_attributes:
- name: Data Input 1
data_point_type: data-point-slug
- name: Constant
constant: 100
- name: Intermediate Calc B
operator: difference
nexus_nodes_attributes:
- name: Static Input
order: 0
data_point_type: static-input-slug
- name: Data Input 2
order: 1
data_point_type: data-point-2-slug
```
This is the equivalent of writing the following equation:
$(Datapoint1 + 100) + (\alpha - Datapoint2) = Output$
As a best practice, **Validate** the model you have built, and check the results in the Console before saving the model.
### Editing models
Saving the model updates its version.
New model versions do not affect the calculations of past accounting batches, only new calculations run after the version has been activated.
## Model versioning
Each model on a ledger can have multiple versions. Only one version per ledger is **active** at a time: the active version is the one Mangrove uses when generating new batches. All other versions are kept as inactive drafts.
### Creating a new draft version
Use draft versions to iterate on a model without disrupting live accounting.
* Open the model and make your edits in the Model Editor
* Click **Save**
* In the save dialog, enter a **Version Label** to name the new version (e.g. `2026 Emissions Update`)
* Optionally add a **Description** of what the version is for or what changed, up to 500 characters
* Leave the **Set as active version** option unchecked to save the edits as an inactive draft. The previously active version continues to drive batch generation.
* Check **Set as active version** on save to activate the new version in one step
You can keep iterating on an inactive draft: adjust nodes, test calculations, and validate the model before promoting it.
### Activating a version
* Open the **version dropdown** on the model to see all versions for the ledger
* Click the **edit icon** next to the version you want to pin
* Update the **Version Label** or **Description** if needed and click **Activate**
* Mangrove atomically switches the active version: the previously active version becomes inactive and the newly activated version is used for subsequent batch generation
A version must validate successfully before it can be activated. Activation is blocked if the model has validation errors.
### Deleting versions
The currently active model version cannot be deleted. To remove an active version, first activate a different version on the same ledger, then delete the now-inactive one.
### How versions interact with batches
* Previously generated batches continue to reference the version that produced them, so historical accounting is unaffected by activating a new version.
* Newly generated batches default to whichever version is active at the time of generation.
* You can override the model version at batch generation time to run batches against a specific draft. This is useful for previewing how a draft will calculate before you activate it. See [Generate batches](/production-accounting/generate-batches).
* The model header and model cards display the active version's number and label so you can see at a glance which version a project is running.
## Defining new nodes
Nodes can be added to the model to represent specific variables in the accounting equations. Each node can:
* Receive a value from a data source (data\_point\_type)
* Use a constant/mapped value from a library, or
* Have its value calculated from child nodes through the defined operator
```yaml Input nodes theme={null}
- name: Input Node
data_point_type: {{input-data-point-slug}}
- name: Static Input Node
data_point_type: {{static-input-slug}}
- name: Constant Node
constant: {{value}}
```
```yaml Intermediate calculated node theme={null}
- name: Calculated Node
operator: summation
nexus_nodes_attributes:
- name: Child Node 1
data_point_type: {{child-data-point-slug}}
- name: Child Node 2
data_point_type: {{child-data-point-slug2}}
```
```yaml Output node theme={null}
- name: Output Node
operator: summation
data_point_type: output-slug
nexus_nodes_attributes:
- name: Intermediate Calc A
...
- name: Intermediate Calc B
...
}
```
See **Reference** below for the full list of parameters that can be defined on each node.
### Units
Units are not always required to be defined on nodes, but are *very* helpful for legibility especially when data is run through operator expressions. Use `output_unit` to define the expected result unit:
```yaml Example with units [expandable] theme={null}
nexus_nodes_attributes:
- name: Calculated EF
output_unit: tCO2e/tonne
operator: quotient
nexus_nodes_attributes:
- name: Emissions
order: 0
output_unit: kgCO2e // note that Mangrove supports unit conversion above
data_point_type: emissions-datapoint-slug
- name: Mass
order: 1
output_unit: U.S. ton // note that Mangrove supports unit conversion above
data_point_type: mass-datapoint-slug
```
See **Reference** below: units have to conform to the supported syntax from the Unitwise library. Mismatched units between inputs on the same tier of nodes can result in errors unless explicitly converted upstream.
## Testing models
Models can be tested by running them with a set of inputs to confirm the calculations.
How to test the models:
* Open the **Model Editor** by clicking on **Edit/Test** besides the model name
* Switch to **Test Model**
* Define a time range (this is necessary for certain calculations that consider duration), enter values for the model's required inputs, and **Run Test**
* Review the output calculations in the Console
## Reference
Below is the full list of fields that can be configured on nodes within a model.
Name of the variable
Unit. We use the Unitwise library to interpret units. Here's a list of [library-supported units](https://github.com/joshwlewis/unitwise/blob/master/data/derived_unit.yaml).
Specifies whether the values of that node represent a type of GHG calculation. Possible values: `co2e`
Specifies the node's order in the parent node's equation
Defines a new tier of child nodes.
Specifies the datapoint slug that the nodes is associated with, defining the node as either an input node that expects incoming data, or an input node tied to a Static Input.
Operation performed on the child nodes. A detailed guide to operations can be found [here](/production-accounting/model-expressions).
| Operator | Function |
| :- | :- |
| Keisan expressions | Mathematical expressions using the Keisan library. See the [Model Expressions guide](/production-accounting/model-expressions) for syntax and examples. |
| summation | Sums all child node values |
| difference | Subtracts subsequent values from the first. Child nodes have to be set in the right `order`. |
| product | Multiplies all child node values |
| quotient | Divides subsequent values from the first. Child nodes have to be set in the right `order`. |
| max | Finds the maximum value across all child node values |
| min | Finds the minimum value across all child node values |
| average | Finds the average of all child node values |
| count | Returns the count of child node values |
| data\_point\_proportion | Special operator used to extract the allocation proportion from an allocated node |
| duration | Special operator used to convert the time range of the incoming datapoint into a duration |
| map | Maps a value from a linked library |
Sets a constant value on the node.
If set to `false`, the node's computation behaviour will change such that it will always return an array in which the computation is applied to each element of the input. If the node is using data points as input, the array will have the same length as the number of data points found for that data flow. If the node is using multiple child nodes as input, the operator is applied piece-wise across each child's output array to derive one output array.
## Further Reading
Read more on how to write mathematical and logical expressions in Mangrove models.
# Uncertainty propagation
Source: https://docs.mangrovesystems.com/production-accounting/uncertainty-propagation
The uncertainty feature in Mangrove allows you to specify measurement uncertainty for data inputs and automatically calculates how uncertainty propagates through model calculations.
Uncertainty can be set on:
* **Datapoint Types**: Uncertainty values that apply to all data points of that type
* **Static Inputs**: Uncertainty values for static (constant) input values
You can also override calculated uncertainty values for specific batch calculations when needed.
***
## Setting Uncertainty on Datapoint Types
Uncertainty settings for datapoint types are configured when creating or editing event types in the Data Inputs settings:
1. Navigate to **Data Inputs** → **Settings**
2. Create a new event type or edit an existing one
3. For each numeric datapoint type, you'll see an **"Add Uncertainty"** button next to the unit field
Creating a "Biochar Delivery" event type with a "Biochar weight" datapoint type measured in tons, with a known 0.1t uncertainty of the weigh scale.
1. Create the event type and add a numeric datapoint type called "Weight" with unit "U.S. ton"
2. Click **"Add Uncertainty"** next to the unit field, and select **Value Type**: "Absolute"
3. Select a + conservative direction (i.e. delivery amounts are conservatively larger)
4. Enter **Value**: `0.1 `
Now, all measurements of biochar weight in deliveries ingested into Mangrove have an uncertainty of ±0.1 tons associated with them.
***
## Setting Uncertainty on Static Inputs
Static inputs are configured in the Data Inputs settings:
1. Navigate to **Data Inputs** → **Settings** → **Static Inputs**
2. Click **"Add Input Type"** to create a new static input, or edit an existing one
3. For numeric static inputs, you'll see an **"Add Uncertainty"** button next to the unit field
***
## Understanding Uncertainty Propagation
Uncertainty automatically propagates through batch calculations. The platform computes uncertainty for each node within the batch calculations, and displays the uncertainty alongside the batch calculations within each Batch detail.
### Propagation Rules
Uncertainty is propagated through calculations according to how the expressions are modeled:
**Uncertainty on inputs:**
* Uncertainty comes from the data points themselves
* If multiple data points are used, their uncertainties are combined
**Propagation within calculations:**
| operator | behavior |
| - | - |
| Summation/Difference | Uncertainty = sum of all child node uncertainties
`uncertainty_total = Σ(uncertainty_child)` |
| Product/Quotient | relative uncertainty formula
`uncertainty = √(Σ(relative_uncertainty²)) × value`
`relative_uncertainty = uncertainty_child / value_child` |
| Max/Min | Uses the uncertainty from the node with the maximum or minimum value |
### Example: Fuel Consumption Emissions
Consider a simple model that calculates total emissions:
```text theme={null}
Total Emissions = (Fuel Consumption × Emission Factor) + Direct Emissions
```
Where:
* Fuel Consumption = 1000 gallons (uncertainty: ±50 gallons)
* Emission Factor = 20 kg CO₂/gallon (uncertainty: ±1 kg CO₂/gallon)
* Direct Emissions = 500 kg CO₂ (uncertainty: ±25 kg CO₂)
**Step 1: Uncertainty propagation in multiplication**
* Value: 1000 × 20 = 20,000 kg CO₂
* Relative uncertainties: 50/1000 = 0.05, 1/20 = 0.05
* Combined relative: √(0.05² + 0.05²) = √0.005 = 0.071
* Uncertainty: 20,000 × 0.071 = ±1,420 kg CO₂
**Step 2: Calculate Total Emissions (Sum)**
* Value: 20,000 + 500 = 20,500 kg CO₂
* Uncertainty: 1,420 + 25 = ±1,445 kg CO₂
**Final Result**: 20,500 kg CO₂ (±1,445 kg CO₂)
***
## Overriding Calculated Uncertainty Values
Sometimes you may want to override the automatically calculated uncertainty for a specific model run. This is useful when you have expert knowledge about the uncertainty that differs from the calculated value.
**How to Override Uncertainty**
1. Navigate to the **Ledger** view and open a batch
2. In the **Batch Calculations Table**, locate the node whose uncertainty you want to override
3. Click on the uncertainty value (displayed as `(±value)` or `(±value%)`)
4. A popover will appear with an input field
5. Enter your custom uncertainty value
6. Click the checkmark (✓) to save, or the delete icon (🗑️) to remove an existing override
***
## Troubleshooting
* Ensure uncertainty is set on the datapoint types used in your events
* Check that data points have values (percentage uncertainty requires a value to convert)
* Verify that the model run has completed successfully
* Review the propagation rules to understand how uncertainty combines
* Check if any overrides are set that might affect the calculation
* Verify that all input uncertainties are correctly configured
* Ensure the datapoint or static input has a value set
* Check that the value type is numeric
* Verify the uncertainty configuration is saved correctly
# Share a report with an auditor
Source: https://docs.mangrovesystems.com/reporting/audit-hub
Give a third-party auditor read-only access to a report, its batches, and its evidence, without sending files by email.
Audit Hub gives the auditor reviewing your report their own way into Mangrove. You invite them by email, and Mangrove sends them everything they need to get in, so there is nothing for you to send separately and no files to email. They sign in, open the report you shared, and work through its batches, documents, and evidence in place. You keep control of who has access and can withdraw it at any time.
Audit Hub is enabled per account. Ask your Mangrove account team to switch it on. Until it is, the **Auditors** card does not appear on a report.
## Inviting an auditor
Open the report in **Reporting** and find the **Auditors** card in the left column. On a report with no auditors yet, click **Invite auditor**. Once one is on the report, the card offers **Manage** instead, which opens the same dialog. Under **Invite an auditor**, enter their email address and click **Send invite**. An email address is all you provide.
In the app, only account admins can invite or remove auditors. Over the API the check is on the token instead: any token with **Accounting and Reporting** write permission can do both, whatever role it belongs to. Treat those tokens accordingly.
Mangrove emails the auditor a link to the specific report you shared. The invitation names the report, its value stream, and the production period, so the auditor knows what they are being asked to review before they accept.
An auditor new to Mangrove fills in their name and chooses a password the first time they follow the link, then lands straight on the report. That first link is good for three days; if it lapses, **Resend** sends a fresh one, which invalidates the link sent before it. Anyone working from the old email has to use the new one. An auditor who already has a Mangrove login goes straight to the report, and their link does not expire.
Inviting the same person again resends the invitation without creating a second grant, so **Resend** and a repeat invitation do the same thing.
An email address that already belongs to a Mangrove dashboard user cannot be invited as an auditor, and neither can one belonging to a deactivated account, which is refused with a message of its own. This is checked across all of Mangrove, not just your account, so an auditor who also works inside another customer's Mangrove account has to be invited under a different address. To give a colleague a look at what the auditor sees, use **Preview auditor view** instead.
An auditor's entry on the card shows where they are:
| Status | Meaning |
| - | - |
| **Invited** | The invitation has been sent and not yet accepted. **Resend** is available. |
| **Active** | The auditor has activated their account and can open the report. |
| **Removed** | Their access to this report has been withdrawn. **Re-invite** is available. |
## Removing and restoring access
**Remove** withdraws an auditor's access to that report straight away. The next time they try to open it they get a message saying it is no longer available, and it drops off their list. Removed auditors move to an **Inactive auditors** section in the **Manage auditors** dialog, where **Re-invite** restores access for anyone whose own account has not been deactivated.
Removing access to one report leaves any other reports you have shared with the same auditor untouched.
## Previewing what the auditor sees
**Preview auditor view** opens the report exactly as the auditor gets it, inside a banner marking the preview. Use it before you invite anyone, particularly to confirm the documents and evidence you expect are attached.
## What the auditor works with
The auditor signs in to Audit Hub and lands on a list of every report shared with them, across every account that has shared one. Opening a report gives them:
* A header with the reporting period, the report total, and the number of batches and documents included.
* **Documents**, **Production batches**, and **Evidence** sections listing everything included in the report.
* A preview pane. Selecting a batch shows its generated calculation workbook in place, and selecting a document or an evidence file previews that. Anything they can preview, they can download individually.
* **Download Report Data (zip)**, which takes the whole package in one file.
Following a link from one workbook to another file in the package opens that file in a drawer, so an auditor tracing a figure back to its source stays inside the report. A link on an event opens a read-only view of that event instead of its CSV, listing the period and each data point with its correction history, so an auditor can see what replaced a value and which method produced it.
Timestamps in the package and in the auditor's view are rendered in the timezone of the account that produced the report, not the auditor's own.
The auditor's own **Profile** page is where they update their name, email, organization, and password.
Auditors have read-only access throughout. They cannot edit data, add comments, upload files, or change the report's state. Mangrove records when an auditor first opens a shared report, so tell them their access is logged.
## The report package
The package is the zip behind both the auditor's inline previews and the download:
| Folder | Contents |
| - | - |
| `batches/` | A calculation workbook per batch in the report, plus one for every upstream batch feeding them. There are usually more workbooks here than the batch count in the report header. |
| `model_workbooks/` | The source workbook behind each workbook-backed model. |
| `events/` | One CSV per event type, carrying the source data. |
| `evidence/` | The evidence files behind the reported data, deduplicated. |
| `documents/` | Every document on the report. |
Batch workbooks and evidence files are named after what you see in the app, with the record's ID in parentheses, so a batch arrives as `June-1-Biochar-Ledger_(ba_abc123def456).xlsx` rather than under its ID alone. Spaces and punctuation become hyphens, which keeps the links between files inside the package working. Documents keep the filename they were uploaded with, and the archive itself is named for the project, the program, and the reporting period.
Each batch workbook opens on its quantification model, then the batch output, then a sheet per model in the batch's lineage, then a **Substitutions Summary**, then a source data sheet per data point type. Where a model computes a vector it does not aggregate, an extra calculation sheet per interval sits behind that model's own sheet, with the arithmetic live in the cells rather than flattened to a single figure.
The source sheets carry the correction columns, so an auditor can see the value as captured, what replaced it, which value the calculation used, and the method applied. Each row carries a start and an end time rather than a single timestamp, because a value covers a period: a monthly meter figure filed for July shows the whole of July rather than its first day. Both columns name the time zone they are rendered in.
**Substitutions Summary** gives the auditor the shape of the corrections before they read any of them: one row per measured data point type, with counts and totals each split into substituted and non-substituted, and the substituted share of each. Every row links through to the source sheet behind it.
Mangrove builds the package when you create the report, and rebuilds it whenever you **add or remove something on the report**, such as a document or a batch. The auditor sees the rebuilt package, so anything you add while a review is under way reaches them without a fresh invitation. The download becomes available once the package is ready.
Changing the **data inside** the report is the case that does not rebuild it. See the warning below.
Changing the underlying data does not rebuild the package. A value you correct after sharing a report stays at its old figure in the auditor's copy until something else triggers a rebuild, so get the data right before you invite anyone, and ask your Mangrove account team for a rebuild if it has to change mid-review.
## Over the API
Two endpoints manage access. Both need a token with **Accounting and Reporting** write permission, and both return `403` until Audit Hub is enabled on the account.
* [Invite an auditor to a report](/api-reference/reports/invite-auditor)
* [Revoke an auditor's access](/api-reference/reports/revoke-auditor)
Inviting is idempotent: sending the same address again resends the invitation rather than creating a second grant. Revoking addresses the auditor by email.
Reading the roster, downloading the package, and everything the auditor portal does are in the Mangrove app rather than on the API.
# Reporting Fundamentals
Source: https://docs.mangrovesystems.com/reporting/fundamentals
Key reporting concepts used in Mangrove
#### Report
A **Report** packages a project's production accounting, documentation, and evidence for a reporting period. Reports are submitted for verification and, once verified, support credit issuance to the project's inventory. They meet value stream and registry requirements and communicate results to auditors and registries.
#### Report Requirement
A **Report Requirement** is a specific requirement that must be included in a Report to meet a value stream or registry's rules. Reports have multiple requirements defined by the registry, such as production accounting, or attached writeups, all of which must be met for review or credit issuance.
#### Submission
**Submission** is the process of sending a completed Report for acceptance or credit issuance. In Mangrove, the submission can be done directly through the platform to connected registries, or recorded for tracking.
#### Verification
**Verification** is the process where a third-party **auditor** reviews the submission and accepts it for credit issuance. Share the report with the auditor through [Audit Hub](/reporting/audit-hub), which gives them read-only access to its batches, documents, and evidence in Mangrove.
#### Credit Issuance
**Credit Issuance** is when a registry or standards body reviews a Report and issues credits to a project's inventory. This follows successful verification. [See Inventory Fundamentals](/inventory/fundamentals#issuance)
#### Issuance Forecast
An **Issuance Forecast** is an estimate of the number and timing of credits likely to be issued based on project activities. Mangrove automatically creates a new forecast when a Report is submitted. It helps stakeholders plan for credit availability and is updated as new data from Reports or schedules becomes available. [See Inventory Fundamentals](/inventory/fundamentals#issuance)
# Sync reporting directly to registries
Source: https://docs.mangrovesystems.com/reporting/integrations
Production data can be directly reported through a connection to select registries and value streams that support integrations.
### Send to Registry
If your project is certified through one of the registries listed below where Mangrove has an integration, Mangrove allows you to directly send reporting to the registry for verification.
* In the dropdown beside the `Generate Verification Report` button, select `Send to Registry`
* Confirm the data you're reporting
* `Submit` to send the data directly to the registry
* The submission and its status are listed on the report's **Submissions** card
### When a registry submission fails
Every send to a registry is listed on the report's **Submissions** card. A send that fails shows as **Failed**, or as **Not sent** when nothing reached the registry, with the reason underneath. Click **View** to open the submission and see:
* **Reason**: what went wrong and the next step to take, tagged **Needs a fix**, **Connection problem** or **Contact support**
* **Details**: a link to the submission log, with every request and response sent to the registry
A failed report can be sent again with **Send to Registry** once the cause is fixed.
### Available Integrations
Mangrove has integrations available to the following registries:
Send reporting directly to Isometric's Certify API
Report stocks and Sinks directly to CSI's Global C-Sink API.
Initiate a Monitoring Report in Verra's Digital Project Submission Hub.
#### Upcoming Integrations
* Puro dMRV API
* MRET-S RTC API
Don't see a value stream that you would like to connect your Mangrove data directly to? Please reach out to us to discuss. Mangrove is in active conversations with a wide range of registries and carbon programs.
# Report project data to a registry
Source: https://docs.mangrovesystems.com/reporting/report-production
Batches on the Production Accounting must be allocated to specific credit issuances to attach the collected production data to that issuance and estimate the resulting credit quantity.
Once a project meets the minimum production threshold to be reported, a Mangrove user can initiate reporting to a registry to get credits issued. Reporting is done periodically, and the user can manage all reports of various stages in the **Reporting** tab.
### The Reporting Lifecycle
* From the Project's **Reporting** tab, click on **Generate Report**
* Select the production period to be reported
* A new draft report is initiated and a set of verification outputs are automatically generated.
* The owner of the Report is set to the user who initiated the draft. Assign the report to other users in the Mangrove account when necessary
Besides the production accounting requirement, a registry often asks for other information to be included, such as a document explaining changes within the reported period, or a list of locations where the project activity took place, or a written discussion of some of the reported data. This information is added to each Requirement on the report.
* Select a particular requirement
* Upload the necessary documents to meet the requirement
The Report can be submitted directly through the platform to connected registries and auditors. Alternatively, Reports can be generated off-platform and then recorded within Mangrove for tracking and audit purposes.
* **Download Package** with all the included documents, and send the downloaded file folder directly to the registry. Use the **Submit** button to record a submission of the Report
* Use the **Send to Registry** button to sync the Report, its documents, calculations and evidence files, directly to the registry for review. See [Reporting Integrations](/reporting/integrations).
* Once the Report is submitted, a new issuance forecast is added to the credit inventory, with an estimated time of issuance.
* After the report is submitted, the auditor or reviewing body reviews the submitted data. This process varies depending on the registry.
* Share the report with the auditor through [Audit Hub](/reporting/audit-hub) to give them read-only access to its batches, documents, and evidence in Mangrove.
* A report can be submitted several times if necessary
* Once the Report is approved, Mark the Report as "Complete" in Mangrove.
* This converts the forecasted credits into a Credit Issuance, ready to be delivered to customer orders
### The Report
Each Report detail view shows you the full list of requirements, the status of each requirement, the status of the Report as it progresses through the review process.
To effectively track a report, update the Report Name, designate the right owner, and leave notes on the Report.
### Auto-generated verification statements
Mangrove auto-generates statements and documents from the reported production data to match templates provided by your registry. This includes:
* Spreadsheet reports
* Embedded dynamic links to a web view of each batch's calculations, and associated evidence files
* PDF statements summarizing the production activity within the period
These generated outputs are compliant with the reporting file formats required by the registry.
Auto-generated statements requires configuration, and may not be available for your specific value stream. Talk to your Mangrove representative to enable this.