curl --request POST \
--url https://sandboxapi.kelviq.com/api/v1/catalog/features/ \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "API Calls",
"featureType": "METER",
"identifier": "api-calls",
"description": "Number of API calls per billing cycle.",
"featureDetails": {
"featureSubType": "PRE_AGGREGATED_USAGE",
"units": {
"singular": "call",
"plural": "calls"
}
},
"meter": {},
"metadata": {}
}
'import requests
url = "https://sandboxapi.kelviq.com/api/v1/catalog/features/"
payload = {
"name": "API Calls",
"featureType": "METER",
"identifier": "api-calls",
"description": "Number of API calls per billing cycle.",
"featureDetails": {
"featureSubType": "PRE_AGGREGATED_USAGE",
"units": {
"singular": "call",
"plural": "calls"
}
},
"meter": {},
"metadata": {}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: 'API Calls',
featureType: 'METER',
identifier: 'api-calls',
description: 'Number of API calls per billing cycle.',
featureDetails: {
featureSubType: 'PRE_AGGREGATED_USAGE',
units: {singular: 'call', plural: 'calls'}
},
meter: {},
metadata: {}
})
};
fetch('https://sandboxapi.kelviq.com/api/v1/catalog/features/', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://sandboxapi.kelviq.com/api/v1/catalog/features/",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => 'API Calls',
'featureType' => 'METER',
'identifier' => 'api-calls',
'description' => 'Number of API calls per billing cycle.',
'featureDetails' => [
'featureSubType' => 'PRE_AGGREGATED_USAGE',
'units' => [
'singular' => 'call',
'plural' => 'calls'
]
],
'meter' => [
],
'metadata' => [
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://sandboxapi.kelviq.com/api/v1/catalog/features/"
payload := strings.NewReader("{\n \"name\": \"API Calls\",\n \"featureType\": \"METER\",\n \"identifier\": \"api-calls\",\n \"description\": \"Number of API calls per billing cycle.\",\n \"featureDetails\": {\n \"featureSubType\": \"PRE_AGGREGATED_USAGE\",\n \"units\": {\n \"singular\": \"call\",\n \"plural\": \"calls\"\n }\n },\n \"meter\": {},\n \"metadata\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://sandboxapi.kelviq.com/api/v1/catalog/features/")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"API Calls\",\n \"featureType\": \"METER\",\n \"identifier\": \"api-calls\",\n \"description\": \"Number of API calls per billing cycle.\",\n \"featureDetails\": {\n \"featureSubType\": \"PRE_AGGREGATED_USAGE\",\n \"units\": {\n \"singular\": \"call\",\n \"plural\": \"calls\"\n }\n },\n \"meter\": {},\n \"metadata\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://sandboxapi.kelviq.com/api/v1/catalog/features/")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"API Calls\",\n \"featureType\": \"METER\",\n \"identifier\": \"api-calls\",\n \"description\": \"Number of API calls per billing cycle.\",\n \"featureDetails\": {\n \"featureSubType\": \"PRE_AGGREGATED_USAGE\",\n \"units\": {\n \"singular\": \"call\",\n \"plural\": \"calls\"\n }\n },\n \"meter\": {},\n \"metadata\": {}\n}"
response = http.request(request)
puts response.read_body{
"id": "3a3e92ab-90a3-4f43-8e87-9d4c8c5a9c01",
"identifier": "api-calls",
"name": "API Calls",
"description": "Number of API calls per billing cycle.",
"featureType": "METER",
"featureDetails": {
"featureSubType": "PRE_AGGREGATED_USAGE",
"units": {
"singular": "call",
"plural": "calls"
}
},
"meter": {},
"details": {},
"metadata": {
"category": "core"
},
"isArchived": false,
"modifiedOn": "2025-04-12T08:21:14.910Z"
}Create a feature
Creates a new feature. For METER features, featureDetails must include a featureSubType (PRE_AGGREGATED_USAGE or RAW_EVENTS) and units. RAW_EVENTS additionally requires a meter object with aggregation and filters.
curl --request POST \
--url https://sandboxapi.kelviq.com/api/v1/catalog/features/ \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "API Calls",
"featureType": "METER",
"identifier": "api-calls",
"description": "Number of API calls per billing cycle.",
"featureDetails": {
"featureSubType": "PRE_AGGREGATED_USAGE",
"units": {
"singular": "call",
"plural": "calls"
}
},
"meter": {},
"metadata": {}
}
'import requests
url = "https://sandboxapi.kelviq.com/api/v1/catalog/features/"
payload = {
"name": "API Calls",
"featureType": "METER",
"identifier": "api-calls",
"description": "Number of API calls per billing cycle.",
"featureDetails": {
"featureSubType": "PRE_AGGREGATED_USAGE",
"units": {
"singular": "call",
"plural": "calls"
}
},
"meter": {},
"metadata": {}
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: 'API Calls',
featureType: 'METER',
identifier: 'api-calls',
description: 'Number of API calls per billing cycle.',
featureDetails: {
featureSubType: 'PRE_AGGREGATED_USAGE',
units: {singular: 'call', plural: 'calls'}
},
meter: {},
metadata: {}
})
};
fetch('https://sandboxapi.kelviq.com/api/v1/catalog/features/', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://sandboxapi.kelviq.com/api/v1/catalog/features/",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => 'API Calls',
'featureType' => 'METER',
'identifier' => 'api-calls',
'description' => 'Number of API calls per billing cycle.',
'featureDetails' => [
'featureSubType' => 'PRE_AGGREGATED_USAGE',
'units' => [
'singular' => 'call',
'plural' => 'calls'
]
],
'meter' => [
],
'metadata' => [
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://sandboxapi.kelviq.com/api/v1/catalog/features/"
payload := strings.NewReader("{\n \"name\": \"API Calls\",\n \"featureType\": \"METER\",\n \"identifier\": \"api-calls\",\n \"description\": \"Number of API calls per billing cycle.\",\n \"featureDetails\": {\n \"featureSubType\": \"PRE_AGGREGATED_USAGE\",\n \"units\": {\n \"singular\": \"call\",\n \"plural\": \"calls\"\n }\n },\n \"meter\": {},\n \"metadata\": {}\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://sandboxapi.kelviq.com/api/v1/catalog/features/")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"API Calls\",\n \"featureType\": \"METER\",\n \"identifier\": \"api-calls\",\n \"description\": \"Number of API calls per billing cycle.\",\n \"featureDetails\": {\n \"featureSubType\": \"PRE_AGGREGATED_USAGE\",\n \"units\": {\n \"singular\": \"call\",\n \"plural\": \"calls\"\n }\n },\n \"meter\": {},\n \"metadata\": {}\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://sandboxapi.kelviq.com/api/v1/catalog/features/")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"API Calls\",\n \"featureType\": \"METER\",\n \"identifier\": \"api-calls\",\n \"description\": \"Number of API calls per billing cycle.\",\n \"featureDetails\": {\n \"featureSubType\": \"PRE_AGGREGATED_USAGE\",\n \"units\": {\n \"singular\": \"call\",\n \"plural\": \"calls\"\n }\n },\n \"meter\": {},\n \"metadata\": {}\n}"
response = http.request(request)
puts response.read_body{
"id": "3a3e92ab-90a3-4f43-8e87-9d4c8c5a9c01",
"identifier": "api-calls",
"name": "API Calls",
"description": "Number of API calls per billing cycle.",
"featureType": "METER",
"featureDetails": {
"featureSubType": "PRE_AGGREGATED_USAGE",
"units": {
"singular": "call",
"plural": "calls"
}
},
"meter": {},
"details": {},
"metadata": {
"category": "core"
},
"isArchived": false,
"modifiedOn": "2025-04-12T08:21:14.910Z"
}Authorizations
The Server API Key obtained from the kelviq application. Pass as a Bearer token in the Authorization header. Example: 'Authorization: Bearer YOUR_API_KEY'
Body
"API Calls"
BOOLEAN, CUSTOMIZABLE, METER "METER"
"api-calls"
"Number of API calls per billing cycle."
{
"featureSubType": "PRE_AGGREGATED_USAGE",
"units": { "singular": "call", "plural": "calls" }
}
{}
{}
Response
Feature created.
A feature that can be granted as an entitlement on a plan.
"3a3e92ab-90a3-4f43-8e87-9d4c8c5a9c01"
URL-safe slug, auto-generated from name if not provided.
"api-calls"
"API Calls"
"Number of API calls per billing cycle."
BOOLEAN: on/off flag. CUSTOMIZABLE: numeric limit. METER: usage-based metering.
BOOLEAN, CUSTOMIZABLE, METER "METER"
Configuration for the feature. For METER features, expects featureSubType (PRE_AGGREGATED_USAGE or RAW_EVENTS), units, and optionally customerUnit for unit conversion.
{
"featureSubType": "PRE_AGGREGATED_USAGE",
"units": { "singular": "call", "plural": "calls" }
}
For RAW_EVENTS meter features: an object with aggregation (function, field) and filters.
{}
{}
{ "category": "core" }
false
"2025-04-12T08:21:14.910Z"