> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.solvimon.com/platform-guides/meter-and-event-design/meter/dynamic-meter-creation/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: " \ -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": "", "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': '', '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", "") 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"] = '' 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 response = Unirest.post("https://test.api.solvimon.com/v1/ingest/meter-data?meter_validation=DYNAMIC") .header("X-API-KEY", "") .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 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' => '', ], ]); 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", ""); 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": "", "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). ---