> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.solvimon.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.solvimon.com/_mcp/server.

# Dynamic meter creation

Ingest events for meters, meter values and meter properties that don't exist yet; Solvimon creates them on the fly as part of processing the event.

---

## Why this matters

Normally an event is rejected when it references a meter, meter value or meter property that hasn't been configured yet. Dynamic meter creation removes that requirement: you can let events drive the meter configuration instead of pre-creating every meter, value and property up front. This is useful when your usage model is still evolving.

## Enabling it per request

The behaviour is controlled by the `meter_validation` query parameter on [POST /v1/ingest/meter-data](https://docs.solvimon.com/api-docs/event-api/ingest/post-ingest-meter-data):

| Value     | Behaviour                                                                                                            |
| --------- | -------------------------------------------------------------------------------------------------------------------- |
| `STATIC`  | The default. The meter, its values and its properties must already exist, otherwise the event is rejected.           |
| `DYNAMIC` | Missing meters, values, properties and ENUM values are created on the fly, then the event is processed against them. |

> **Info**
>
> `meter_validation` defaults to `STATIC`, so existing integrations are unaffected. Only requests that explicitly pass `meter_validation=DYNAMIC` create resources on the fly.

## What gets created

When a `DYNAMIC` event is ingested, Solvimon ensures every referenced resource exists before processing the event:

* **Meter**: created when the `meter_reference` is unknown.
* **Meter value**: each referenced value that doesn't exist is created.
* **Meter property**: each referenced property that doesn't exist is created.
* **ENUM value**: if a referenced property already exists and is of type `ENUM`, but the submitted value isn't in its list of allowed values, the value is added to the list.

If the meter already exists, it is extended with any values, properties or ENUM values that are still missing.

The created resources use these defaults:

| Resource       | reference                                 | name                  | status   | type                                                                                                                             |
| -------------- | ----------------------------------------- | --------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------- |
| Meter          | the `meter_reference` from the event      | same as the reference | `ACTIVE` | –                                                                                                                                |
| Meter value    | the value's `reference` from the event    | same as the reference | `ACTIVE` | derived from the field used in the event: `AMOUNT` if the value is submitted as an `amount`, `NUMBER` if submitted as a `number` |
| Meter property | the property's `reference` from the event | same as the reference | `ACTIVE` | `STRING` (covers all values) unless overridden                                                                                   |

Values and properties are linked to the meter as **not required** by default, so a dynamically-created meter never blocks subsequent events.

## Controlling type and required

By default a created meter property is a `STRING` and every association is optional. You can override this per value or property with an optional `dynamic_validation_settings` object on the `meter_values[]` and `meter_properties[]` entries of the event. These settings are only used when the resource is created on the fly and are ignored when it already exists.

| Field      | Applies to     | Description                                                                                                                                              |
| ---------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`     | meter value    | `NUMBER` or `AMOUNT`. When omitted, the type follows the field used in the event: `amount` creates an `AMOUNT` value, `number` creates a `NUMBER` value. |
| `type`     | meter property | `NUMBER`, `ENUM` or `STRING`. When omitted, defaults to `STRING`. For `ENUM`, the submitted `value` is added to the allowed values.                      |
| `required` | both           | Whether the value/property is required on the meter. Defaults to `false`.                                                                                |

## Example

The following event creates the `meter_subscriptions` meter (if it doesn't exist), a required `ENUM` property `plan` seeded with the value `pro`, and a `NUMBER` value `seats`, all in a single ingest call:

### Request

POST [https://test.api.solvimon.com/v\{version}/ingest/meter-data](https://test.api.solvimon.com/v\{version}/ingest/meter-data)

```curl
curl -X POST "https://test.api.solvimon.com/v1/ingest/meter-data?meter_validation=DYNAMIC" \
     -H "X-API-KEY: <apiKey>" \
     -H "Content-Type: application/json" \
     -d '{
  "reference": "00124578951",
  "customer_reference": "9923725805",
  "meter_reference": "meter_subscriptions",
  "timestamp": "2024-07-01T10:50:00+02:00",
  "meter_properties": [
    {
      "reference": "plan",
      "value": "pro",
      "dynamic_validation_settings": {
        "type": "ENUM",
        "required": true
      }
    }
  ],
  "meter_values": [
    {
      "reference": "seats",
      "number": "25"
    }
  ]
}'
```

```python
import requests

url = "https://test.api.solvimon.com/v1/ingest/meter-data"

querystring = {"meter_validation":"DYNAMIC"}

payload = {
    "reference": "00124578951",
    "customer_reference": "9923725805",
    "meter_reference": "meter_subscriptions",
    "timestamp": "2024-07-01T10:50:00+02:00",
    "meter_properties": [
        {
            "reference": "plan",
            "value": "pro",
            "dynamic_validation_settings": {
                "type": "ENUM",
                "required": True
            }
        }
    ],
    "meter_values": [
        {
            "reference": "seats",
            "number": "25"
        }
    ]
}
headers = {
    "X-API-KEY": "<apiKey>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers, params=querystring)

print(response.json())
```

```javascript
const url = 'https://test.api.solvimon.com/v1/ingest/meter-data?meter_validation=DYNAMIC';
const options = {
  method: 'POST',
  headers: {'X-API-KEY': '<apiKey>', 'Content-Type': 'application/json'},
  body: '{"reference":"00124578951","customer_reference":"9923725805","meter_reference":"meter_subscriptions","timestamp":"2024-07-01T10:50:00+02:00","meter_properties":[{"reference":"plan","value":"pro","dynamic_validation_settings":{"type":"ENUM","required":true}}],"meter_values":[{"reference":"seats","number":"25"}]}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://test.api.solvimon.com/v1/ingest/meter-data?meter_validation=DYNAMIC"

	payload := strings.NewReader("{\n  \"reference\": \"00124578951\",\n  \"customer_reference\": \"9923725805\",\n  \"meter_reference\": \"meter_subscriptions\",\n  \"timestamp\": \"2024-07-01T10:50:00+02:00\",\n  \"meter_properties\": [\n    {\n      \"reference\": \"plan\",\n      \"value\": \"pro\",\n      \"dynamic_validation_settings\": {\n        \"type\": \"ENUM\",\n        \"required\": true\n      }\n    }\n  ],\n  \"meter_values\": [\n    {\n      \"reference\": \"seats\",\n      \"number\": \"25\"\n    }\n  ]\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("X-API-KEY", "<apiKey>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://test.api.solvimon.com/v1/ingest/meter-data?meter_validation=DYNAMIC")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["X-API-KEY"] = '<apiKey>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"reference\": \"00124578951\",\n  \"customer_reference\": \"9923725805\",\n  \"meter_reference\": \"meter_subscriptions\",\n  \"timestamp\": \"2024-07-01T10:50:00+02:00\",\n  \"meter_properties\": [\n    {\n      \"reference\": \"plan\",\n      \"value\": \"pro\",\n      \"dynamic_validation_settings\": {\n        \"type\": \"ENUM\",\n        \"required\": true\n      }\n    }\n  ],\n  \"meter_values\": [\n    {\n      \"reference\": \"seats\",\n      \"number\": \"25\"\n    }\n  ]\n}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://test.api.solvimon.com/v1/ingest/meter-data?meter_validation=DYNAMIC")
  .header("X-API-KEY", "<apiKey>")
  .header("Content-Type", "application/json")
  .body("{\n  \"reference\": \"00124578951\",\n  \"customer_reference\": \"9923725805\",\n  \"meter_reference\": \"meter_subscriptions\",\n  \"timestamp\": \"2024-07-01T10:50:00+02:00\",\n  \"meter_properties\": [\n    {\n      \"reference\": \"plan\",\n      \"value\": \"pro\",\n      \"dynamic_validation_settings\": {\n        \"type\": \"ENUM\",\n        \"required\": true\n      }\n    }\n  ],\n  \"meter_values\": [\n    {\n      \"reference\": \"seats\",\n      \"number\": \"25\"\n    }\n  ]\n}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://test.api.solvimon.com/v1/ingest/meter-data?meter_validation=DYNAMIC', [
  'body' => '{
  "reference": "00124578951",
  "customer_reference": "9923725805",
  "meter_reference": "meter_subscriptions",
  "timestamp": "2024-07-01T10:50:00+02:00",
  "meter_properties": [
    {
      "reference": "plan",
      "value": "pro",
      "dynamic_validation_settings": {
        "type": "ENUM",
        "required": true
      }
    }
  ],
  "meter_values": [
    {
      "reference": "seats",
      "number": "25"
    }
  ]
}',
  'headers' => [
    'Content-Type' => 'application/json',
    'X-API-KEY' => '<apiKey>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://test.api.solvimon.com/v1/ingest/meter-data?meter_validation=DYNAMIC");
var request = new RestRequest(Method.POST);
request.AddHeader("X-API-KEY", "<apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"reference\": \"00124578951\",\n  \"customer_reference\": \"9923725805\",\n  \"meter_reference\": \"meter_subscriptions\",\n  \"timestamp\": \"2024-07-01T10:50:00+02:00\",\n  \"meter_properties\": [\n    {\n      \"reference\": \"plan\",\n      \"value\": \"pro\",\n      \"dynamic_validation_settings\": {\n        \"type\": \"ENUM\",\n        \"required\": true\n      }\n    }\n  ],\n  \"meter_values\": [\n    {\n      \"reference\": \"seats\",\n      \"number\": \"25\"\n    }\n  ]\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "X-API-KEY": "<apiKey>",
  "Content-Type": "application/json"
]
let parameters = [
  "reference": "00124578951",
  "customer_reference": "9923725805",
  "meter_reference": "meter_subscriptions",
  "timestamp": "2024-07-01T10:50:00+02:00",
  "meter_properties": [
    [
      "reference": "plan",
      "value": "pro",
      "dynamic_validation_settings": [
        "type": "ENUM",
        "required": true
      ]
    ]
  ],
  "meter_values": [
    [
      "reference": "seats",
      "number": "25"
    ]
  ]
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://test.api.solvimon.com/v1/ingest/meter-data?meter_validation=DYNAMIC")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### Response (201)

```json
{
  "object_type": "meter_data",
  "id": "md_9876543210",
  "reference": "00124578951",
  "type": "INGEST",
  "customer_reference": "9923725805",
  "meter_reference": "meter_subscriptions",
  "timestamp": "2024-07-01T10:50:00+02:00",
  "meter_properties": [
    {
      "reference": "plan",
      "value": "pro"
    }
  ],
  "meter_values": [
    {
      "reference": "seats",
      "number": "25"
    }
  ]
}
```

This ensures the meter, the `plan` property (ENUM, required, with `pro` as an allowed value) and the `seats` value (NUMBER) all exist before the event is processed.

## Inspect and tighten a created meter in Desk

A meter that grew out of your event stream can be turned into a deliberate definition without recreating it. The **Show meter** action on the meter dropdown opens a side panel where you can:

* see the meter with the values and properties ingestion created for it,
* mark values and properties as required, closing the permissive default above,
* add new values and properties, the same ones you could create by ingesting them,
* extend an existing `ENUM` property with more allowed values.

Until this panel existed a dynamically-created meter was usable but not inspectable: it appeared only as a name in a dropdown. Note that [enum values cannot be removed](/platform-guides/meter-and-event-design/meter/meter-properties) once the property is active, since they may already be linked to usage data.

## Idempotency and concurrency

Resource creation is idempotent and safe to retry. If two events for the same new meter (or value/property) arrive at the same time, only one resource is created: the other request detects the duplicate reference and reuses the resource that won the race, so no event fails because of a creation conflict.

## Notes and limitations

* `meter_validation=DYNAMIC` only affects ingestion. It does not change how meters behave once created; a dynamically-created meter is an ordinary meter that you can manage in Desk or via [POST /v1/meters](https://docs.solvimon.com/api-docs/configuration-api/meters/post-meters).
* For an existing meter, the creation step only runs when the event actually references something missing; otherwise ingestion follows the normal path.
* The submitted value still has to be valid for the (existing or newly-created) value or property type. For example, a `NUMBER` value must be a valid number.

See also: [Meters](/platform-guides/meter-and-event-design/meter), [Meter values](/platform-guides/meter-and-event-design/meter/meter-values), [Meter properties](/platform-guides/meter-and-event-design/meter/meter-properties) and the [Event ingestion API](/platform-guides/meter-and-event-design/usage-events/event-ingestion-api).

---