Skip to main content

APIs

Data model

The feature structure depends on the type of the strategy. A feature can have the following strategies:

  • NO_STRATEGY: active or inactive
  • RELEASE_DATE: active on a specific date
  • DATE_RANGE: active between two date
  • SCRIPT: Activation depends on the execution of a js script
  • GLOBAL_SCRIPT: Activation depends on the execution of a js script shared across feature
  • PERCENTAGE: Active for a percentage of client

NO_STRATEGY format

{
"id": "basic:feature",
"enabled": true,
"activationStrategy": "NO_STRATEGY"
}

RELEASE_DATE format

{
"id": "feature:with:date",
"enabled": true,
"parameters": {
"releaseDate": "13/12/2017 10:29:04"
},
"activationStrategy": "RELEASE_DATE"
}

DATE_RANGE format

{
"id": "feature:with:datetange",
"enabled": true,
"parameters": {
"from": "2017-12-13 10:29:04",
"to": "2017-12-14 00:45:23"
},
"activationStrategy": "DATE_RANGE"
}

HOUR_RANGE format

{
"id": "feature:with:hour:range",
"enabled": true,
"parameters": {
"startDate": "10:15",
"endDate": "18:30"
},
"activationStrategy": "HOUR_RANGE"
}

SCRIPT format

The field typecan be

  • javascript
  • kotlin
  • scala
{
"id": "feature:with:script",
"enabled": true,
"parameters": {
"type": "javascript",
"script": "/**\n * context: a JSON object containing app specific value \n * to evaluate the state of the feature\n * enabled: a callback to mark the feature as active \n * for this request\n * disabled: a callback to mark the feature as inactive \n * for this request \n * http: a http client\n */ \nfunction enabled(context, enabled, disabled, http) {\n if (context.user === 'ragnar.lodbrok@gmail.com') {\n return enabled();\n }\n return disabled();\n}"
},
"activationStrategy": "SCRIPT"
}

GLOBAL_SCRIPT format

{
"id": "feature:with:global:script",
"enabled": false,
"parameters": {
"ref": "project:script:ref"
},
"activationStrategy": "GLOBAL_SCRIPT"
}

PERCENTAGE format

{
"id": "feature:with:percentage",
"enabled": false,
"parameters": {
"percentage": 60
},
"activationStrategy": "PERCENTAGE"
}

CRUD API

Features expose a classic CRUD REST api :

List all

curl -X GET \
'http://localhost:9000/api/features?pattern=feature:*&page=1&pageSize=15' \
-H 'Content-Type: application/json' \
-H 'Izanami-Client-Id: xxxx' \
-H 'Izanami-Client-Secret: xxxx' | jq

Will respond with a 200 status code:

{
"results": [
{
"id": "feature:with:date",
"enabled": true,
"parameters": {
"releaseDate": "13/12/2017 10:29:04"
},
"activationStrategy": "RELEASE_DATE"
},
{
"id": "feature:with:script",
"enabled": true,
"parameters": {
"type": "javascript",
"script": "/**\n * context: a JSON object containing app specific value \n * to evaluate the state of the feature\n * enabled: a callback to mark the feature as active \n * for this request\n * disabled: a callback to mark the feature as inactive \n * for this request \n * http: a http client\n */ \nfunction enabled(context, enabled, disabled, http) {\n if (context.user === 'ragnar.lodbrok@gmail.com') {\n return enabled();\n }\n return disabled();\n}"
},
"activationStrategy": "SCRIPT"
},
{
"id": "feature:with:global:script",
"enabled": true,
"parameters": {
"ref": "project:script2"
},
"activationStrategy": "GLOBAL_SCRIPT"
}
],
"metadata": {
"page": 1,
"pageSize": 15,
"count": 3,
"nbPages": 1
}
}

The query params are optional and the value used in this example are the default one.

Create a feature

curl -XPOST \
'http://localhost:9000/api/features' \
-H 'Content-Type: application/json' \
-H 'Izanami-Client-Id: xxxx' \
-H 'Izanami-Client-Secret: xxxx' \
-d '{ "id": "really:basic:feature", "enabled": true, "activationStrategy": "NO_STRATEGY" }' | jq

Will respond with a 201 status code:

{
"id": "really:basic:feature",
"enabled": true,
"activationStrategy": "NO_STRATEGY"
}

Get a feature

curl -X GET \
'http://localhost:9000/api/features/really:basic:feature' \
-H 'Content-Type: application/json' \
-H 'Izanami-Client-Id: xxxx' \
-H 'Izanami-Client-Secret: xxxx' | jq

Will respond with a 200 status code:

{
"id": "really:basic:feature",
"enabled": true,
"activationStrategy": "NO_STRATEGY"
}

Update a feature

curl -X PUT \
'http://localhost:9000/api/features/really:basic:feature' \
-H 'Content-Type: application/json' \
-H 'Izanami-Client-Id: xxxx' \
-H 'Izanami-Client-Secret: xxxx' \
-d '{ "id": "really:basic:feature", "enabled": false, "activationStrategy": "NO_STRATEGY" }' | jq

Will respond with a 200 status code:

{
"id": "really:basic:feature",
"enabled": false,
"activationStrategy": "NO_STRATEGY"
}

Patch a feature

Partial updates can be done using json patch :

curl -X PATCH \
'http://localhost:9000/api/features/really:basic:feature' \
-H 'Content-Type: application/json' \
-H 'Izanami-Client-Id: xxxx' \
-H 'Izanami-Client-Secret: xxxx' \
-d '[{"op": "replace", "path": "enabled", "value": false}]'

Will respond with a 200 status code:

{
"id": "really:basic:feature",
"enabled": false,
"activationStrategy": "NO_STRATEGY"
}

Delete a feature

curl -X DELETE \
'http://localhost:9000/api/features/really:basic:feature' \
-H 'Content-Type: application/json' \
-H 'Izanami-Client-Id: xxxx' \
-H 'Izanami-Client-Secret: xxxx' | jq

Will respond with a 200 status code:

{
"id": "really:basic:feature",
"enabled": false,
"activationStrategy": "NO_STRATEGY"
}

As you can see, the keys are split with : and the values are

Check features

We can divide the feature in two kind :

  • feature without context:
    • NO_STRATEGY features
    • RELEASE_DATE features
    • DATE_RANGE features
    • HOUR_RANGE features
  • feature needing context:
    • SCRIPT features
    • GLOBAL_SCRIPT features
    • PERCENTAGE features

Check a feature without context

curl -X GET \
'http://localhost:9000/api/features/really:basic:feature/check' \
-H 'Content-Type: application/json' \
-H 'Izanami-Client-Id: xxxx' \
-H 'Izanami-Client-Secret: xxxx' | jq

Will respond with a 200 status code:

{
"active": true
}

Check a feature with context

curl -X POST \
'http://localhost:9000/api/features/feature:with:script/check' \
-H 'Content-Type: application/json' \
-H 'Izanami-Client-Id: xxxx' \
-H 'Izanami-Client-Secret: xxxx' \
-d '{ "user": "ragnar.lodbrok@gmail.com" }' | jq

Will respond with a 200 status code:

{
"active": true
}

The percentage strategy need a context with an id field in order to decide if the feature is active for this id.

curl -X POST \
'http://localhost:9000/api/features/feature:with:percentage/check' \
-H 'Content-Type: application/json' \
-H 'Izanami-Client-Id: xxxx' \
-H 'Izanami-Client-Secret: xxxx' \
-d '{ "id": "ragnar.lodbrok@gmail.com" }' | jq

Will respond with a 200 status code:

{
"active": true
}

The Tree API

With the tree api you can check the features and get the results as tree. Be careful using this API because the results are not paged so be sure to use an appropriate pattern :

Check the features without context

curl -X GET \
'http://localhost:9000/api/tree/features?pattern=feature:*' \
-H 'Content-Type: application/json' \
-H 'Izanami-Client-Id: xxxx' \
-H 'Izanami-Client-Secret: xxxx' | jq

We will get the following response:

{
"feature": {
"simple": {
"active": true
},
"with": {
"date": {
"active": true
},
"script": {
"active": false
},
"global": {
"script": {
"active": false
}
}
}
}
}

Or with context:

curl -X POST \
'http://localhost:9000/api/tree/features?pattern=feature:*' \
-H 'Content-Type: application/json' \
-H 'Izanami-Client-Id: xxxx' \
-H 'Izanami-Client-Secret: xxxx' \
-d '{ "user": "ragnar.lodbrok@gmail.com" }' \
| jq
{
"feature": {
"simple": {
"active": true
},
"with": {
"date": {
"active": true
},
"script": {
"active": false
},
"global": {
"script": {
"active": true
}
}
}
}
}

List features with the active attribute without context

curl -X GET \
'http://localhost:9000/api/features?pattern=feature:*&active=true' \
-H 'Content-Type: application/json' \
-H 'Izanami-Client-Id: xxxx' \
-H 'Izanami-Client-Secret: xxxx' | jq

The result is the following

{
"results": [
{
"id": "feature:simple",
"enabled": true,
"activationStrategy": "NO_STRATEGY",
"active": true
},
{
"id": "feature:with:date",
"enabled": true,
"parameters": {
"releaseDate": "13/12/2017 10:29:04"
},
"activationStrategy": "RELEASE_DATE",
"active": true
},
{
"id": "feature:with:script",
"enabled": true,
"parameters": {
"type": "javascript",
"script": "/**\n * context: a JSON object containing app specific value \n * to evaluate the state of the feature\n * enabled: a callback to mark the feature as active \n * for this request\n * disabled: a callback to mark the feature as inactive \n * for this request \n * http: a http client\n */ \nfunction enabled(context, enabled, disabled, http) {\n if (context.user === 'ragnar.lodbrok@gmail.com') {\n return enabled();\n }\n return disabled();\n}"
},
"activationStrategy": "SCRIPT",
"active": false
},
{
"id": "feature:with:global:script",
"enabled": true,
"parameters": {
"ref": "project:script2"
},
"activationStrategy": "GLOBAL_SCRIPT",
"active": false
}
],
"metadata": {
"page": 1,
"pageSize": 15,
"count": 4,
"nbPages": 1
}
}

List features with the active attribute with context

curl -X POST \
'http://localhost:9000/api/features/_checks?pattern=feature:*' \
-H 'Content-Type: application/json' \
-H 'Izanami-Client-Id: xxxx' \
-H 'Izanami-Client-Secret: xxxx' \
-d '{ "user": "ragnar.lodbrok@gmail.com" }' \
| jq

The result is the following

{
"results": [
{
"id": "feature:simple",
"enabled": true,
"activationStrategy": "NO_STRATEGY",
"active": true
},
{
"id": "feature:with:date",
"enabled": true,
"parameters": {
"releaseDate": "13/12/2017 10:29:04"
},
"activationStrategy": "RELEASE_DATE",
"active": true
},
{
"id": "feature:with:script",
"enabled": true,
"parameters": {
"type": "javascript",
"script": "/**\n * context: a JSON object containing app specific value \n * to evaluate the state of the feature\n * enabled: a callback to mark the feature as active \n * for this request\n * disabled: a callback to mark the feature as inactive \n * for this request \n * http: a http client\n */ \nfunction enabled(context, enabled, disabled, http) {\n if (context.user === 'ragnar.lodbrok@gmail.com') {\n return enabled();\n }\n return disabled();\n}"
},
"activationStrategy": "SCRIPT",
"active": true
},
{
"id": "feature:with:global:script",
"enabled": true,
"parameters": {
"ref": "project:script2"
},
"activationStrategy": "GLOBAL_SCRIPT",
"active": false
}
],
"metadata": {
"page": 1,
"pageSize": 15,
"count": 4,
"nbPages": 1
}
}

Download and Upload

Download and upload is done using the nd-json format.

Download

curl -X GET \
'http://localhost:9000/api/features.ndjson' \
-H 'Izanami-Client-Id: xxxx' \
-H 'Izanami-Client-Secret: xxxx' | jq

Will return each item separated by a \n.

{
"id": "feature:simple1",
"enabled": true,
"activationStrategy": "NO_STRATEGY"
}
{
"id": "feature:simple2",
"enabled": true,
"activationStrategy": "NO_STRATEGY"
}

Upload

curl -X POST \
'http://localhost:9000/api/features.ndjson' \
-H 'Content-Type: application/nd-json' \
-H 'Izanami-Client-Id: xxxx' \
-H 'Izanami-Client-Secret: xxxx' \
-d '{"id": "feature:simple1","enabled": true,"activationStrategy": "NO_STRATEGY","active": true}
{"id": "feature:simple2","enabled": true,"activationStrategy": "NO_STRATEGY","active": true}' \
| jq

Will return

{
"success": 2,
"errors": {
"errors": [],
"fieldErrors": {}
}
}