Преглед изворни кода

v3 capi and blocklists links support (#2019)

* v3 model generation

* v3 model generation

* comms

* fixes after master merge

* missing reader close

* use constants defined for types

---------

Co-authored-by: bui <thibault@crowdsec.net>
Cristian Nitescu пре 2 година
родитељ
комит
987f119c4b
38 измењених фајлова са 3965 додато и 78 уклоњено
  1. 1 4
      cmd/crowdsec-cli/capi.go
  2. 1 1
      cmd/crowdsec-cli/console.go
  3. 5 1
      pkg/apiclient/client_test.go
  4. 132 1
      pkg/apiclient/decisions_service.go
  5. 258 0
      pkg/apiclient/decisions_service_test.go
  6. 108 17
      pkg/apiserver/apic.go
  7. 59 53
      pkg/apiserver/apic_test.go
  8. 4 1
      pkg/database/alerts.go
  9. 75 0
      pkg/modelscapi/add_signals_request.go
  10. 361 0
      pkg/modelscapi/add_signals_request_item.go
  11. 73 0
      pkg/modelscapi/add_signals_request_item_decisions.go
  12. 179 0
      pkg/modelscapi/add_signals_request_item_decisions_item.go
  13. 109 0
      pkg/modelscapi/add_signals_request_item_source.go
  14. 139 0
      pkg/modelscapi/blocklist_link.go
  15. 67 0
      pkg/modelscapi/decisions_delete_request.go
  16. 27 0
      pkg/modelscapi/decisions_delete_request_item.go
  17. 75 0
      pkg/modelscapi/decisions_sync_request.go
  18. 263 0
      pkg/modelscapi/decisions_sync_request_item.go
  19. 73 0
      pkg/modelscapi/decisions_sync_request_item_decisions.go
  20. 179 0
      pkg/modelscapi/decisions_sync_request_item_decisions_item.go
  21. 109 0
      pkg/modelscapi/decisions_sync_request_item_source.go
  22. 87 0
      pkg/modelscapi/enroll_request.go
  23. 76 0
      pkg/modelscapi/error_response.go
  24. 190 0
      pkg/modelscapi/get_decisions_stream_response.go
  25. 73 0
      pkg/modelscapi/get_decisions_stream_response_deleted.go
  26. 88 0
      pkg/modelscapi/get_decisions_stream_response_deleted_item.go
  27. 116 0
      pkg/modelscapi/get_decisions_stream_response_links.go
  28. 73 0
      pkg/modelscapi/get_decisions_stream_response_new.go
  29. 226 0
      pkg/modelscapi/get_decisions_stream_response_new_item.go
  30. 108 0
      pkg/modelscapi/login_request.go
  31. 58 0
      pkg/modelscapi/login_response.go
  32. 180 0
      pkg/modelscapi/metrics_request.go
  33. 59 0
      pkg/modelscapi/metrics_request_bouncers_item.go
  34. 59 0
      pkg/modelscapi/metrics_request_machines_item.go
  35. 95 0
      pkg/modelscapi/register_request.go
  36. 105 0
      pkg/modelscapi/reset_password_request.go
  37. 73 0
      pkg/modelscapi/success_response.go
  38. 2 0
      pkg/types/constants.go

+ 1 - 4
cmd/crowdsec-cli/capi.go

@@ -20,8 +20,7 @@ import (
 )
 
 const CAPIBaseURL string = "https://api.crowdsec.net/"
-const CAPIURLPrefix = "v2"
-
+const CAPIURLPrefix = "v3"
 
 func NewCapiCmd() *cobra.Command {
 	var cmdCapi = &cobra.Command{
@@ -47,7 +46,6 @@ func NewCapiCmd() *cobra.Command {
 	return cmdCapi
 }
 
-
 func NewCapiRegisterCmd() *cobra.Command {
 	var capiUserPrefix string
 	var outputFile string
@@ -122,7 +120,6 @@ func NewCapiRegisterCmd() *cobra.Command {
 	return cmdCapiRegister
 }
 
-
 func NewCapiStatusCmd() *cobra.Command {
 	var cmdCapiStatus = &cobra.Command{
 		Use:               "status",

+ 1 - 1
cmd/crowdsec-cli/console.go

@@ -102,7 +102,7 @@ After running this command your will need to validate the enrollment in the weba
 				Scenarios:     scenarios,
 				UserAgent:     fmt.Sprintf("crowdsec/%s", cwversion.VersionStr()),
 				URL:           apiURL,
-				VersionPrefix: "v2",
+				VersionPrefix: "v3",
 			})
 			resp, err := c.Auth.EnrollWatcher(context.Background(), args[0], name, tags, overwrite)
 			if err != nil {

+ 5 - 1
pkg/apiclient/client_test.go

@@ -21,9 +21,13 @@ import (
 */
 
 func setup() (mux *http.ServeMux, serverURL string, teardown func()) {
+	return setupWithPrefix("v1")
+}
+
+func setupWithPrefix(urlPrefix string) (mux *http.ServeMux, serverURL string, teardown func()) {
 	// mux is the HTTP request multiplexer used with the test server.
 	mux = http.NewServeMux()
-	baseURLPath := "/v1"
+	baseURLPath := "/" + urlPrefix
 
 	apiHandler := http.NewServeMux()
 	apiHandler.Handle(baseURLPath+"/", http.StripPrefix(baseURLPath, mux))

+ 132 - 1
pkg/apiclient/decisions_service.go

@@ -1,12 +1,18 @@
 package apiclient
 
 import (
+	"bufio"
 	"context"
 	"fmt"
+	"io"
 	"net/http"
 
 	"github.com/crowdsecurity/crowdsec/pkg/models"
+	"github.com/crowdsecurity/crowdsec/pkg/modelscapi"
+	"github.com/crowdsecurity/crowdsec/pkg/types"
 	qs "github.com/google/go-querystring/query"
+	"github.com/pkg/errors"
+	log "github.com/sirupsen/logrus"
 )
 
 type DecisionsService service
@@ -67,15 +73,140 @@ func (s *DecisionsService) List(ctx context.Context, opts DecisionsListOpts) (*m
 	if err != nil {
 		return nil, resp, err
 	}
+
 	return &decisions, resp, nil
 }
 
-func (s *DecisionsService) GetStream(ctx context.Context, opts DecisionsStreamOpts) (*models.DecisionsStreamResponse, *Response, error) {
+func (s *DecisionsService) FetchV2Decisions(ctx context.Context, url string) (*models.DecisionsStreamResponse, *Response, error) {
 	var decisions models.DecisionsStreamResponse
+
+	req, err := s.client.NewRequest(http.MethodGet, url, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	resp, err := s.client.Do(ctx, req, &decisions)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return &decisions, resp, nil
+}
+
+func (s *DecisionsService) GetDecisionsFromGroups(decisionsGroups []*modelscapi.GetDecisionsStreamResponseNewItem) []*models.Decision {
+	var decisions []*models.Decision
+
+	for _, decisionsGroup := range decisionsGroups {
+		partialDecisions := make([]*models.Decision, len(decisionsGroup.Decisions))
+		for idx, decision := range decisionsGroup.Decisions {
+			partialDecisions[idx] = &models.Decision{
+				Scenario: decisionsGroup.Scenario,
+				Scope:    decisionsGroup.Scope,
+				Type:     types.StrPtr(types.DecisionTypeBan),
+				Value:    decision.Value,
+				Duration: decision.Duration,
+				Origin:   types.StrPtr(types.CAPIOrigin),
+			}
+		}
+		decisions = append(decisions, partialDecisions...)
+	}
+	return decisions
+}
+
+func (s *DecisionsService) FetchV3Decisions(ctx context.Context, url string) (*models.DecisionsStreamResponse, *Response, error) {
+	var decisions modelscapi.GetDecisionsStreamResponse
+	var v2Decisions models.DecisionsStreamResponse
+
+	scenarioDeleted := "deleted"
+	durationDeleted := "1h"
+
+	req, err := s.client.NewRequest(http.MethodGet, url, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	resp, err := s.client.Do(ctx, req, &decisions)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	v2Decisions.New = s.GetDecisionsFromGroups(decisions.New)
+	for _, decisionsGroup := range decisions.Deleted {
+		partialDecisions := make([]*models.Decision, len(decisionsGroup.Decisions))
+		for idx, decision := range decisionsGroup.Decisions {
+			decision := decision // fix exportloopref linter message
+			partialDecisions[idx] = &models.Decision{
+				Scenario: &scenarioDeleted,
+				Scope:    decisionsGroup.Scope,
+				Type:     types.StrPtr(types.DecisionTypeBan),
+				Value:    &decision,
+				Duration: &durationDeleted,
+				Origin:   types.StrPtr(types.CAPIOrigin),
+			}
+		}
+		v2Decisions.Deleted = append(v2Decisions.Deleted, partialDecisions...)
+	}
+
+	return &v2Decisions, resp, nil
+}
+
+func (s *DecisionsService) GetDecisionsFromBlocklist(ctx context.Context, blocklist *modelscapi.BlocklistLink) ([]*models.Decision, error) {
+	if blocklist.URL == nil {
+		return nil, errors.New("blocklist URL is nil")
+	}
+
+	log.Debugf("Fetching blocklist %s", *blocklist.URL)
+
+	req, err := s.client.NewRequest(http.MethodGet, *blocklist.URL, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	pr, pw := io.Pipe()
+	defer pr.Close()
+	go func() {
+		defer pw.Close()
+		_, err = s.client.Do(ctx, req, pw)
+		if err != nil {
+			log.Errorf("Error fetching blocklist %s: %s", *blocklist.URL, err)
+		}
+	}()
+	decisions := make([]*models.Decision, 0)
+	scanner := bufio.NewScanner(pr)
+	for scanner.Scan() {
+		decision := scanner.Text()
+		decisions = append(decisions, &models.Decision{
+			Scenario: blocklist.Name,
+			Scope:    blocklist.Scope,
+			Type:     blocklist.Remediation,
+			Value:    &decision,
+			Duration: blocklist.Duration,
+			Origin:   types.StrPtr(types.ListOrigin),
+		})
+	}
+
+	return decisions, nil
+}
+
+func (s *DecisionsService) GetStream(ctx context.Context, opts DecisionsStreamOpts) (*models.DecisionsStreamResponse, *Response, error) {
 	u, err := opts.addQueryParamsToURL(s.client.URLPrefix + "/decisions/stream")
 	if err != nil {
 		return nil, nil, err
 	}
+	if s.client.URLPrefix == "v3" {
+		return s.FetchV3Decisions(ctx, u)
+	} else {
+		return s.FetchV2Decisions(ctx, u)
+	}
+}
+
+func (s *DecisionsService) GetStreamV3(ctx context.Context, opts DecisionsStreamOpts) (*modelscapi.GetDecisionsStreamResponse, *Response, error) {
+	u, err := opts.addQueryParamsToURL(s.client.URLPrefix + "/decisions/stream")
+	if err != nil {
+		return nil, nil, err
+	}
+	var decisions modelscapi.GetDecisionsStreamResponse
+
 	req, err := s.client.NewRequest(http.MethodGet, u, nil)
 	if err != nil {
 		return nil, nil, err

+ 258 - 0
pkg/apiclient/decisions_service_test.go

@@ -10,6 +10,7 @@ import (
 
 	"github.com/crowdsecurity/crowdsec/pkg/cwversion"
 	"github.com/crowdsecurity/crowdsec/pkg/models"
+	"github.com/crowdsecurity/crowdsec/pkg/modelscapi"
 	log "github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -192,6 +193,263 @@ func TestDecisionsStream(t *testing.T) {
 	}
 }
 
+func TestDecisionsStreamV3Compatibility(t *testing.T) {
+	log.SetLevel(log.DebugLevel)
+
+	mux, urlx, teardown := setupWithPrefix("v3")
+	defer teardown()
+
+	mux.HandleFunc("/decisions/stream", func(w http.ResponseWriter, r *http.Request) {
+
+		assert.Equal(t, r.Header.Get("X-Api-Key"), "ixu")
+		testMethod(t, r, http.MethodGet)
+		if r.Method == http.MethodGet {
+			if r.URL.RawQuery == "startup=true" {
+				w.WriteHeader(http.StatusOK)
+				w.Write([]byte(`{"deleted":[{"scope":"ip","decisions":["1.2.3.5"]}],"new":[{"scope":"ip", "scenario": "manual 'ban' from '82929df7ee394b73b81252fe3b4e50203yaT2u6nXiaN7Ix9'", "decisions":[{"duration":"3h59m55.756182786s","value":"1.2.3.4"}]}]}`))
+			} else {
+				w.WriteHeader(http.StatusOK)
+				w.Write([]byte(`{"deleted":null,"new":null}`))
+			}
+		}
+	})
+
+	apiURL, err := url.Parse(urlx + "/")
+	if err != nil {
+		t.Fatalf("parsing api url: %s", apiURL)
+	}
+
+	//ok answer
+	auth := &APIKeyTransport{
+		APIKey: "ixu",
+	}
+
+	newcli, err := NewDefaultClient(apiURL, "v3", "toto", auth.Client())
+	if err != nil {
+		t.Fatalf("new api client: %s", err)
+	}
+
+	tduration := "3h59m55.756182786s"
+	torigin := "CAPI"
+	tscenario := "manual 'ban' from '82929df7ee394b73b81252fe3b4e50203yaT2u6nXiaN7Ix9'"
+	tscope := "ip"
+	ttype := "ban"
+	tvalue := "1.2.3.4"
+	tvalue1 := "1.2.3.5"
+	tscenarioDeleted := "deleted"
+	tdurationDeleted := "1h"
+	expected := &models.DecisionsStreamResponse{
+		New: models.GetDecisionsResponse{
+			&models.Decision{
+				Duration: &tduration,
+				Origin:   &torigin,
+				Scenario: &tscenario,
+				Scope:    &tscope,
+				Type:     &ttype,
+				Value:    &tvalue,
+			},
+		},
+		Deleted: models.GetDecisionsResponse{
+			&models.Decision{
+				Duration: &tdurationDeleted,
+				Origin:   &torigin,
+				Scenario: &tscenarioDeleted,
+				Scope:    &tscope,
+				Type:     &ttype,
+				Value:    &tvalue1,
+			},
+		},
+	}
+
+	// GetStream is supposed to consume v3 payload and return v2 response
+	decisions, resp, err := newcli.Decisions.GetStream(context.Background(), DecisionsStreamOpts{Startup: true})
+	require.NoError(t, err)
+
+	if resp.Response.StatusCode != http.StatusOK {
+		t.Errorf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusOK)
+	}
+
+	if err != nil {
+		t.Fatalf("new api client: %s", err)
+	}
+	if !reflect.DeepEqual(*decisions, *expected) {
+		t.Fatalf("returned %+v, want %+v", resp, expected)
+	}
+}
+
+func TestDecisionsStreamV3(t *testing.T) {
+	log.SetLevel(log.DebugLevel)
+
+	mux, urlx, teardown := setupWithPrefix("v3")
+	defer teardown()
+
+	mux.HandleFunc("/decisions/stream", func(w http.ResponseWriter, r *http.Request) {
+
+		assert.Equal(t, r.Header.Get("X-Api-Key"), "ixu")
+		testMethod(t, r, http.MethodGet)
+		if r.Method == http.MethodGet {
+			w.WriteHeader(http.StatusOK)
+			w.Write([]byte(`{"deleted":[{"scope":"ip","decisions":["1.2.3.5"]}],
+			"new":[{"scope":"ip", "scenario": "manual 'ban' from '82929df7ee394b73b81252fe3b4e50203yaT2u6nXiaN7Ix9'", "decisions":[{"duration":"3h59m55.756182786s","value":"1.2.3.4"}]}],
+			"links": {"blocklists":[{"name":"blocklist1","url":"/v3/blocklist","scope":"ip","remediation":"ban","duration":"24h"}]}}`))
+		}
+	})
+
+	apiURL, err := url.Parse(urlx + "/")
+	if err != nil {
+		t.Fatalf("parsing api url: %s", apiURL)
+	}
+
+	//ok answer
+	auth := &APIKeyTransport{
+		APIKey: "ixu",
+	}
+
+	newcli, err := NewDefaultClient(apiURL, "v3", "toto", auth.Client())
+	if err != nil {
+		t.Fatalf("new api client: %s", err)
+	}
+
+	tduration := "3h59m55.756182786s"
+	tscenario := "manual 'ban' from '82929df7ee394b73b81252fe3b4e50203yaT2u6nXiaN7Ix9'"
+	tscope := "ip"
+	tvalue := "1.2.3.4"
+	tvalue1 := "1.2.3.5"
+	tdurationBlocklist := "24h"
+	tnameBlocklist := "blocklist1"
+	tremediationBlocklist := "ban"
+	tscopeBlocklist := "ip"
+	turlBlocklist := "/v3/blocklist"
+	expected := &modelscapi.GetDecisionsStreamResponse{
+		New: modelscapi.GetDecisionsStreamResponseNew{
+			&modelscapi.GetDecisionsStreamResponseNewItem{
+				Decisions: []*modelscapi.GetDecisionsStreamResponseNewItemDecisionsItems0{
+					{
+						Duration: &tduration,
+						Value:    &tvalue,
+					},
+				},
+				Scenario: &tscenario,
+				Scope:    &tscope,
+			},
+		},
+		Deleted: modelscapi.GetDecisionsStreamResponseDeleted{
+			&modelscapi.GetDecisionsStreamResponseDeletedItem{
+				Scope: &tscope,
+				Decisions: []string{
+					tvalue1,
+				},
+			},
+		},
+		Links: &modelscapi.GetDecisionsStreamResponseLinks{
+			Blocklists: []*modelscapi.BlocklistLink{
+				{
+					Duration:    &tdurationBlocklist,
+					Name:        &tnameBlocklist,
+					Remediation: &tremediationBlocklist,
+					Scope:       &tscopeBlocklist,
+					URL:         &turlBlocklist,
+				},
+			},
+		},
+	}
+
+	// GetStream is supposed to consume v3 payload and return v2 response
+	decisions, resp, err := newcli.Decisions.GetStreamV3(context.Background(), DecisionsStreamOpts{Startup: true})
+	require.NoError(t, err)
+
+	if resp.Response.StatusCode != http.StatusOK {
+		t.Errorf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusOK)
+	}
+
+	if err != nil {
+		t.Fatalf("new api client: %s", err)
+	}
+	if !reflect.DeepEqual(*decisions, *expected) {
+		t.Fatalf("returned %+v, want %+v", resp, expected)
+	}
+}
+
+func TestDecisionsFromBlocklist(t *testing.T) {
+	log.SetLevel(log.DebugLevel)
+
+	mux, urlx, teardown := setupWithPrefix("v3")
+	defer teardown()
+
+	mux.HandleFunc("/blocklist", func(w http.ResponseWriter, r *http.Request) {
+
+		assert.Equal(t, r.Header.Get("X-Api-Key"), "ixu")
+		testMethod(t, r, http.MethodGet)
+		if r.Method == http.MethodGet {
+			w.WriteHeader(http.StatusOK)
+			w.Write([]byte("1.2.3.4\r\n1.2.3.5"))
+		}
+	})
+
+	apiURL, err := url.Parse(urlx + "/")
+	if err != nil {
+		t.Fatalf("parsing api url: %s", apiURL)
+	}
+
+	//ok answer
+	auth := &APIKeyTransport{
+		APIKey: "ixu",
+	}
+
+	newcli, err := NewDefaultClient(apiURL, "v3", "toto", auth.Client())
+	if err != nil {
+		t.Fatalf("new api client: %s", err)
+	}
+
+	tvalue1 := "1.2.3.4"
+	tvalue2 := "1.2.3.5"
+	tdurationBlocklist := "24h"
+	tnameBlocklist := "blocklist1"
+	tremediationBlocklist := "ban"
+	tscopeBlocklist := "ip"
+	turlBlocklist := "/v3/blocklist"
+	torigin := "lists"
+	expected := []*models.Decision{
+		{
+			Duration: &tdurationBlocklist,
+			Value:    &tvalue1,
+			Scenario: &tnameBlocklist,
+			Scope:    &tscopeBlocklist,
+			Type:     &tremediationBlocklist,
+			Origin:   &torigin,
+		},
+		{
+			Duration: &tdurationBlocklist,
+			Value:    &tvalue2,
+			Scenario: &tnameBlocklist,
+			Scope:    &tscopeBlocklist,
+			Type:     &tremediationBlocklist,
+			Origin:   &torigin,
+		},
+	}
+	decisions, err := newcli.Decisions.GetDecisionsFromBlocklist(context.Background(), &modelscapi.BlocklistLink{
+		URL:         &turlBlocklist,
+		Scope:       &tscopeBlocklist,
+		Remediation: &tremediationBlocklist,
+		Name:        &tnameBlocklist,
+		Duration:    &tdurationBlocklist,
+	})
+	require.NoError(t, err)
+
+	log.Infof("decision1: %+v", decisions[0])
+	log.Infof("expected1: %+v", expected[0])
+	log.Infof("decisions: %s, %s, %s, %s, %s, %s", *decisions[0].Value, *decisions[0].Duration, *decisions[0].Scenario, *decisions[0].Scope, *decisions[0].Type, *decisions[0].Origin)
+	log.Infof("expected : %s, %s, %s, %s, %s", *expected[0].Value, *expected[0].Duration, *expected[0].Scenario, *expected[0].Scope, *expected[0].Type)
+	log.Infof("decisions: %s, %s, %s, %s, %s", *decisions[1].Value, *decisions[1].Duration, *decisions[1].Scenario, *decisions[1].Scope, *decisions[1].Type)
+
+	if err != nil {
+		t.Fatalf("new api client: %s", err)
+	}
+	if !reflect.DeepEqual(decisions, expected) {
+		t.Fatalf("returned %+v, want %+v", decisions, expected)
+	}
+}
+
 func TestDeleteDecisions(t *testing.T) {
 	mux, urlx, teardown := setup()
 	mux.HandleFunc("/watchers/login", func(w http.ResponseWriter, r *http.Request) {

+ 108 - 17
pkg/apiserver/apic.go

@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"math/rand"
+	"net/http"
 	"net/url"
 	"strconv"
 	"strings"
@@ -22,6 +23,7 @@ import (
 	"github.com/crowdsecurity/crowdsec/pkg/database/ent/alert"
 	"github.com/crowdsecurity/crowdsec/pkg/database/ent/decision"
 	"github.com/crowdsecurity/crowdsec/pkg/models"
+	"github.com/crowdsecurity/crowdsec/pkg/modelscapi"
 	"github.com/crowdsecurity/crowdsec/pkg/types"
 )
 
@@ -188,7 +190,7 @@ func NewAPIC(config *csconfig.OnlineApiClientCfg, dbClient *database.Client, con
 		UserAgent:      fmt.Sprintf("crowdsec/%s", cwversion.VersionStr()),
 		URL:            apiURL,
 		PapiURL:        papiURL,
-		VersionPrefix:  "v2",
+		VersionPrefix:  "v3",
 		Scenarios:      ret.scenarioList,
 		UpdateScenario: ret.FetchScenariosListFromDB,
 	})
@@ -380,11 +382,41 @@ func (a *apic) HandleDeletedDecisions(deletedDecisions []*models.Decision, delet
 		if err != nil {
 			return 0, errors.Wrapf(err, "converting db ret %d", dbCliDel)
 		}
-		updateCounterForDecision(delete_counters, decision, dbCliDel)
+		updateCounterForDecision(delete_counters, decision.Origin, decision.Scenario, dbCliDel)
 		nbDeleted += dbCliDel
 	}
 	return nbDeleted, nil
+}
+
+func (a *apic) HandleDeletedDecisionsV3(deletedDecisions []*modelscapi.GetDecisionsStreamResponseDeletedItem, delete_counters map[string]map[string]int) (int, error) {
+	var filter map[string][]string
+	var nbDeleted int
+	for _, decisions := range deletedDecisions {
+		scope := decisions.Scope
+		for _, decision := range decisions.Decisions {
+			if strings.ToLower(*scope) == "ip" {
+				filter = make(map[string][]string, 1)
+				filter["value"] = []string{decision}
+			} else {
+				filter = make(map[string][]string, 2)
+				filter["value"] = []string{decision}
+				filter["scopes"] = []string{*scope}
+			}
+			filter["origin"] = []string{types.CAPIOrigin}
 
+			dbCliRet, _, err := a.dbClient.SoftDeleteDecisionsWithFilter(filter)
+			if err != nil {
+				return 0, errors.Wrap(err, "deleting decisions error")
+			}
+			dbCliDel, err := strconv.Atoi(dbCliRet)
+			if err != nil {
+				return 0, errors.Wrapf(err, "converting db ret %d", dbCliDel)
+			}
+			updateCounterForDecision(delete_counters, types.StrPtr(types.CAPIOrigin), nil, dbCliDel)
+			nbDeleted += dbCliDel
+		}
+	}
+	return nbDeleted, nil
 }
 
 func createAlertsForDecisions(decisions []*models.Decision) []*models.Alert {
@@ -454,7 +486,7 @@ func createAlertForDecision(decision *models.Decision) *models.Alert {
 func fillAlertsWithDecisions(alerts []*models.Alert, decisions []*models.Decision, add_counters map[string]map[string]int) []*models.Alert {
 	for _, decision := range decisions {
 		//count and create separate alerts for each list
-		updateCounterForDecision(add_counters, decision, 1)
+		updateCounterForDecision(add_counters, decision.Origin, decision.Scenario, 1)
 
 		/*CAPI might send lower case scopes, unify it.*/
 		switch strings.ToLower(*decision.Scope) {
@@ -489,7 +521,7 @@ func fillAlertsWithDecisions(alerts []*models.Alert, decisions []*models.Decisio
 	return alerts
 }
 
-// we receive only one list of decisions, that we need to break-up :
+// we receive a list of decisions and links for blocklist and we need to create a list of alerts :
 // one alert for "community blocklist"
 // one alert per list we're subscribed to
 func (a *apic) PullTop() error {
@@ -503,7 +535,7 @@ func (a *apic) PullTop() error {
 
 	log.Infof("Starting community-blocklist update")
 
-	data, _, err := a.apiClient.Decisions.GetStream(context.Background(), apiclient.DecisionsStreamOpts{Startup: a.startup})
+	data, _, err := a.apiClient.Decisions.GetStreamV3(context.Background(), apiclient.DecisionsStreamOpts{Startup: a.startup})
 	if err != nil {
 		return errors.Wrap(err, "get stream")
 	}
@@ -512,10 +544,13 @@ func (a *apic) PullTop() error {
 
 	log.Debugf("Received %d new decisions", len(data.New))
 	log.Debugf("Received %d deleted decisions", len(data.Deleted))
+	if data.Links != nil {
+		log.Debugf("Received %d blocklists links", len(data.Links.Blocklists))
+	}
 
 	add_counters, delete_counters := makeAddAndDeleteCounters()
 	// process deleted decisions
-	if nbDeleted, err := a.HandleDeletedDecisions(data.Deleted, delete_counters); err != nil {
+	if nbDeleted, err := a.HandleDeletedDecisionsV3(data.Deleted, delete_counters); err != nil {
 		return err
 	} else {
 		log.Printf("capi/community-blocklist : %d explicit deletions", nbDeleted)
@@ -526,12 +561,25 @@ func (a *apic) PullTop() error {
 		return nil
 	}
 
-	// we receive only one list of decisions, that we need to break-up :
-	// one alert for "community blocklist"
-	// one alert per list we're subscribed to
-	alertsFromCapi := createAlertsForDecisions(data.New)
-	alertsFromCapi = fillAlertsWithDecisions(alertsFromCapi, data.New, add_counters)
+	// create one alert for community blocklist using the first decision
+	decisions := a.apiClient.Decisions.GetDecisionsFromGroups(data.New)
+	alert := createAlertForDecision(decisions[0])
+	alertsFromCapi := []*models.Alert{alert}
+	alertsFromCapi = fillAlertsWithDecisions(alertsFromCapi, decisions, add_counters)
+
+	err = a.SaveAlerts(alertsFromCapi, add_counters, delete_counters)
+	if err != nil {
+		return errors.Wrap(err, "while saving alerts")
+	}
+
+	// update blocklists
+	if err := a.UpdateBlocklists(data.Links, add_counters); err != nil {
+		return errors.Wrap(err, "while updating blocklists")
+	}
+	return nil
+}
 
+func (a *apic) SaveAlerts(alertsFromCapi []*models.Alert, add_counters map[string]map[string]int, delete_counters map[string]map[string]int) error {
 	for idx, alert := range alertsFromCapi {
 		alertsFromCapi[idx] = setAlertScenario(add_counters, delete_counters, alert)
 		log.Debugf("%s has %d decisions", *alertsFromCapi[idx].Source.Scope, len(alertsFromCapi[idx].Decisions))
@@ -544,6 +592,49 @@ func (a *apic) PullTop() error {
 		}
 		log.Printf("%s : added %d entries, deleted %d entries (alert:%d)", *alertsFromCapi[idx].Source.Scope, inserted, deleted, alertID)
 	}
+
+	return nil
+}
+
+func (a *apic) UpdateBlocklists(links *modelscapi.GetDecisionsStreamResponseLinks, add_counters map[string]map[string]int) error {
+	if links == nil {
+		return nil
+	}
+	if links.Blocklists == nil {
+		return nil
+	}
+	// we must use a different http client than apiClient's because the transport of apiClient is jwtTransport or here we have signed apis that are incompatibles
+	// we can use the same baseUrl as the urls are absolute and the parse will take care of it
+	defaultClient, err := apiclient.NewDefaultClient(a.apiClient.BaseURL, "", "", &http.Client{})
+	if err != nil {
+		return errors.Wrap(err, "while creating default client")
+	}
+	for _, blocklist := range links.Blocklists {
+		if blocklist.Scope == nil {
+			log.Warningf("blocklist has no scope")
+			continue
+		}
+		if blocklist.Duration == nil {
+			log.Warningf("blocklist has no duration")
+			continue
+		}
+		decisions, err := defaultClient.Decisions.GetDecisionsFromBlocklist(context.Background(), blocklist)
+		if err != nil {
+			return errors.Wrapf(err, "while getting decisions from blocklist %s", *blocklist.Name)
+		}
+		if len(decisions) == 0 {
+			log.Infof("blocklist %s has no decisions", *blocklist.Name)
+			continue
+		}
+		alert := createAlertForDecision(decisions[0])
+		alertsFromCapi := []*models.Alert{alert}
+		alertsFromCapi = fillAlertsWithDecisions(alertsFromCapi, decisions, add_counters)
+
+		err = a.SaveAlerts(alertsFromCapi, add_counters, nil)
+		if err != nil {
+			return errors.Wrapf(err, "while saving alert from blocklist %s", *blocklist.Name)
+		}
+	}
 	return nil
 }
 
@@ -691,12 +782,12 @@ func makeAddAndDeleteCounters() (map[string]map[string]int, map[string]map[strin
 	return add_counters, delete_counters
 }
 
-func updateCounterForDecision(counter map[string]map[string]int, decision *models.Decision, totalDecisions int) {
-	if *decision.Origin == types.CAPIOrigin {
-		counter[*decision.Origin]["all"] += totalDecisions
-	} else if *decision.Origin == types.ListOrigin {
-		counter[*decision.Origin][*decision.Scenario] += totalDecisions
+func updateCounterForDecision(counter map[string]map[string]int, origin *string, scenario *string, totalDecisions int) {
+	if *origin == types.CAPIOrigin {
+		counter[*origin]["all"] += totalDecisions
+	} else if *origin == types.ListOrigin {
+		counter[*origin][*scenario] += totalDecisions
 	} else {
-		log.Warningf("Unknown origin %s", *decision.Origin)
+		log.Warningf("Unknown origin %s", *origin)
 	}
 }

+ 59 - 53
pkg/apiserver/apic_test.go

@@ -26,6 +26,7 @@ import (
 	"github.com/crowdsecurity/crowdsec/pkg/database/ent/decision"
 	"github.com/crowdsecurity/crowdsec/pkg/database/ent/machine"
 	"github.com/crowdsecurity/crowdsec/pkg/models"
+	"github.com/crowdsecurity/crowdsec/pkg/modelscapi"
 	"github.com/crowdsecurity/crowdsec/pkg/types"
 )
 
@@ -220,7 +221,7 @@ func TestNewAPIC(t *testing.T) {
 			setConfig()
 			httpmock.Activate()
 			defer httpmock.DeactivateAndReset()
-			httpmock.RegisterResponder("POST", "http://foobar/v2/watchers/login", httpmock.NewBytesResponder(
+			httpmock.RegisterResponder("POST", "http://foobar/v3/watchers/login", httpmock.NewBytesResponder(
 				200, jsonMarshalX(
 					models.WatcherAuthResponse{
 						Code:   200,
@@ -518,7 +519,7 @@ func TestFillAlertsWithDecisions(t *testing.T) {
 func TestAPICPullTop(t *testing.T) {
 	api := getAPIC(t)
 	api.dbClient.Ent.Decision.Create().
-		SetOrigin(types.ListOrigin).
+		SetOrigin(types.CAPIOrigin).
 		SetType("ban").
 		SetValue("9.9.9.9").
 		SetScope("Ip").
@@ -531,62 +532,65 @@ func TestAPICPullTop(t *testing.T) {
 	defer httpmock.DeactivateAndReset()
 	httpmock.RegisterResponder("GET", "http://api.crowdsec.net/api/decisions/stream", httpmock.NewBytesResponder(
 		200, jsonMarshalX(
-			models.DecisionsStreamResponse{
-				Deleted: models.GetDecisionsResponse{
-					&models.Decision{
-						Origin:   types.StrPtr(types.ListOrigin),
-						Scenario: types.StrPtr("crowdsecurity/ssh-bf"),
-						Value:    types.StrPtr("9.9.9.9"),
-						Scope:    types.StrPtr("Ip"),
-						Duration: types.StrPtr("24h"),
-						Type:     types.StrPtr("ban"),
+			modelscapi.GetDecisionsStreamResponse{
+				Deleted: modelscapi.GetDecisionsStreamResponseDeleted{
+					&modelscapi.GetDecisionsStreamResponseDeletedItem{
+						Decisions: []string{
+							"9.9.9.9", // This is already present in DB
+							"9.1.9.9", // This not present in DB
+						},
+						Scope: types.StrPtr("Ip"),
 					}, // This is already present in DB
-					&models.Decision{
-						Origin:   types.StrPtr(types.ListOrigin),
-						Scenario: types.StrPtr("crowdsecurity/ssh-bf"),
-						Value:    types.StrPtr("9.1.9.9"),
-						Scope:    types.StrPtr("Ip"),
-						Duration: types.StrPtr("24h"),
-						Type:     types.StrPtr("ban"),
-					}, // This not present in DB.
 				},
-				New: models.GetDecisionsResponse{
-					&models.Decision{
-						Origin:   types.StrPtr(types.CAPIOrigin),
+				New: modelscapi.GetDecisionsStreamResponseNew{
+					&modelscapi.GetDecisionsStreamResponseNewItem{
 						Scenario: types.StrPtr("crowdsecurity/test1"),
-						Value:    types.StrPtr("1.2.3.4"),
 						Scope:    types.StrPtr("Ip"),
-						Duration: types.StrPtr("24h"),
-						Type:     types.StrPtr("ban"),
+						Decisions: []*modelscapi.GetDecisionsStreamResponseNewItemDecisionsItems0{
+							{
+								Value:    types.StrPtr("1.2.3.4"),
+								Duration: types.StrPtr("24h"),
+							},
+						},
 					},
-					&models.Decision{
-						Origin:   types.StrPtr(types.CAPIOrigin),
+					&modelscapi.GetDecisionsStreamResponseNewItem{
 						Scenario: types.StrPtr("crowdsecurity/test2"),
-						Value:    types.StrPtr("1.2.3.5"),
 						Scope:    types.StrPtr("Ip"),
-						Duration: types.StrPtr("24h"),
-						Type:     types.StrPtr("ban"),
+						Decisions: []*modelscapi.GetDecisionsStreamResponseNewItemDecisionsItems0{
+							{
+								Value:    types.StrPtr("1.2.3.5"),
+								Duration: types.StrPtr("24h"),
+							},
+						},
 					}, // These two are from community list.
-					&models.Decision{
-						Origin:   types.StrPtr(types.ListOrigin),
-						Scenario: types.StrPtr("crowdsecurity/http-bf"),
-						Value:    types.StrPtr("1.2.3.6"),
-						Scope:    types.StrPtr("Ip"),
-						Duration: types.StrPtr("24h"),
-						Type:     types.StrPtr("ban"),
+				},
+				Links: &modelscapi.GetDecisionsStreamResponseLinks{
+					Blocklists: []*modelscapi.BlocklistLink{
+						{
+							URL:         types.StrPtr("http://api.crowdsec.net/blocklist1"),
+							Name:        types.StrPtr("crowdsecurity/http-bf"),
+							Scope:       types.StrPtr("Ip"),
+							Remediation: types.StrPtr("ban"),
+							Duration:    types.StrPtr("24h"),
+						},
+						{
+							URL:         types.StrPtr("http://api.crowdsec.net/blocklist2"),
+							Name:        types.StrPtr("crowdsecurity/ssh-bf"),
+							Scope:       types.StrPtr("Ip"),
+							Remediation: types.StrPtr("ban"),
+							Duration:    types.StrPtr("24h"),
+						},
 					},
-					&models.Decision{
-						Origin:   types.StrPtr(types.ListOrigin),
-						Scenario: types.StrPtr("crowdsecurity/ssh-bf"),
-						Value:    types.StrPtr("1.2.3.7"),
-						Scope:    types.StrPtr("Ip"),
-						Duration: types.StrPtr("24h"),
-						Type:     types.StrPtr("ban"),
-					}, // These two are from list subscription.
 				},
 			},
 		),
 	))
+	httpmock.RegisterResponder("GET", "http://api.crowdsec.net/blocklist1", httpmock.NewStringResponder(
+		200, "1.2.3.6",
+	))
+	httpmock.RegisterResponder("GET", "http://api.crowdsec.net/blocklist2", httpmock.NewStringResponder(
+		200, "1.2.3.7",
+	))
 	url, err := url.ParseRequestURI("http://api.crowdsec.net/")
 	require.NoError(t, err)
 
@@ -845,15 +849,17 @@ func TestAPICPull(t *testing.T) {
 			require.NoError(t, err)
 			api.apiClient = apic
 			httpmock.RegisterNoResponder(httpmock.NewBytesResponder(200, jsonMarshalX(
-				models.DecisionsStreamResponse{
-					New: models.GetDecisionsResponse{
-						&models.Decision{
-							Origin:   types.StrPtr(types.CAPIOrigin),
-							Scenario: types.StrPtr("crowdsecurity/test2"),
-							Value:    types.StrPtr("1.2.3.5"),
+				modelscapi.GetDecisionsStreamResponse{
+					New: modelscapi.GetDecisionsStreamResponseNew{
+						&modelscapi.GetDecisionsStreamResponseNewItem{
+							Scenario: types.StrPtr("crowdsecurity/ssh-bf"),
 							Scope:    types.StrPtr("Ip"),
-							Duration: types.StrPtr("24h"),
-							Type:     types.StrPtr("ban"),
+							Decisions: []*modelscapi.GetDecisionsStreamResponseNewItemDecisionsItems0{
+								{
+									Value:    types.StrPtr("1.2.3.5"),
+									Duration: types.StrPtr("24h"),
+								},
+							},
 						},
 					},
 				},

+ 4 - 1
pkg/database/alerts.go

@@ -276,7 +276,10 @@ func (c *Client) CreateAlert(machineID string, alertList []*models.Alert) ([]str
 	return ret, nil
 }
 
-/*We can't bulk both the alert and the decision at the same time. With new consensus, we want to bulk a single alert with a lot of decisions.*/
+// UpdateCommunityBlocklist is called to update either the community blocklist (or other lists the user subscribed to)
+// it takes care of creating the new alert with the associated decisions, and it will as well deleted the "older" overlapping decisions:
+// 1st pull, you get decisions [1,2,3]. it inserts [1,2,3]
+// 2nd pull, you get decisions [1,2,3,4]. it inserts [1,2,3,4] and will try to delete [1,2,3,4] with a different alert ID and same origin
 func (c *Client) UpdateCommunityBlocklist(alertItem *models.Alert) (int, int, int, error) {
 
 	var err error

+ 75 - 0
pkg/modelscapi/add_signals_request.go

@@ -0,0 +1,75 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package modelscapi
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+	"strconv"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+)
+
+// AddSignalsRequest add signals request
+//
+// # All signals request model
+//
+// swagger:model AddSignalsRequest
+type AddSignalsRequest []*AddSignalsRequestItem
+
+// Validate validates this add signals request
+func (m AddSignalsRequest) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	for i := 0; i < len(m); i++ {
+		if swag.IsZero(m[i]) { // not required
+			continue
+		}
+
+		if m[i] != nil {
+			if err := m[i].Validate(formats); err != nil {
+				if ve, ok := err.(*errors.Validation); ok {
+					return ve.ValidateName(strconv.Itoa(i))
+				} else if ce, ok := err.(*errors.CompositeError); ok {
+					return ce.ValidateName(strconv.Itoa(i))
+				}
+				return err
+			}
+		}
+
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+// ContextValidate validate this add signals request based on the context it is used
+func (m AddSignalsRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	var res []error
+
+	for i := 0; i < len(m); i++ {
+
+		if m[i] != nil {
+			if err := m[i].ContextValidate(ctx, formats); err != nil {
+				if ve, ok := err.(*errors.Validation); ok {
+					return ve.ValidateName(strconv.Itoa(i))
+				} else if ce, ok := err.(*errors.CompositeError); ok {
+					return ce.ValidateName(strconv.Itoa(i))
+				}
+				return err
+			}
+		}
+
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}

+ 361 - 0
pkg/modelscapi/add_signals_request_item.go

@@ -0,0 +1,361 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package modelscapi
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+	"strconv"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+	"github.com/go-openapi/validate"
+)
+
+// AddSignalsRequestItem Signal
+//
+// swagger:model AddSignalsRequestItem
+type AddSignalsRequestItem struct {
+
+	// alert id
+	AlertID int64 `json:"alert_id,omitempty"`
+
+	// context
+	Context []*AddSignalsRequestItemContextItems0 `json:"context"`
+
+	// created at
+	CreatedAt string `json:"created_at,omitempty"`
+
+	// decisions
+	Decisions AddSignalsRequestItemDecisions `json:"decisions,omitempty"`
+
+	// machine id
+	MachineID string `json:"machine_id,omitempty"`
+
+	// a human readable message
+	// Required: true
+	Message *string `json:"message"`
+
+	// scenario
+	// Required: true
+	Scenario *string `json:"scenario"`
+
+	// scenario hash
+	// Required: true
+	ScenarioHash *string `json:"scenario_hash"`
+
+	// scenario trust
+	ScenarioTrust string `json:"scenario_trust,omitempty"`
+
+	// scenario version
+	// Required: true
+	ScenarioVersion *string `json:"scenario_version"`
+
+	// source
+	// Required: true
+	Source *AddSignalsRequestItemSource `json:"source"`
+
+	// start at
+	// Required: true
+	StartAt *string `json:"start_at"`
+
+	// stop at
+	// Required: true
+	StopAt *string `json:"stop_at"`
+}
+
+// Validate validates this add signals request item
+func (m *AddSignalsRequestItem) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.validateContext(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateDecisions(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateMessage(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateScenario(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateScenarioHash(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateScenarioVersion(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateSource(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateStartAt(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateStopAt(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *AddSignalsRequestItem) validateContext(formats strfmt.Registry) error {
+	if swag.IsZero(m.Context) { // not required
+		return nil
+	}
+
+	for i := 0; i < len(m.Context); i++ {
+		if swag.IsZero(m.Context[i]) { // not required
+			continue
+		}
+
+		if m.Context[i] != nil {
+			if err := m.Context[i].Validate(formats); err != nil {
+				if ve, ok := err.(*errors.Validation); ok {
+					return ve.ValidateName("context" + "." + strconv.Itoa(i))
+				} else if ce, ok := err.(*errors.CompositeError); ok {
+					return ce.ValidateName("context" + "." + strconv.Itoa(i))
+				}
+				return err
+			}
+		}
+
+	}
+
+	return nil
+}
+
+func (m *AddSignalsRequestItem) validateDecisions(formats strfmt.Registry) error {
+	if swag.IsZero(m.Decisions) { // not required
+		return nil
+	}
+
+	if err := m.Decisions.Validate(formats); err != nil {
+		if ve, ok := err.(*errors.Validation); ok {
+			return ve.ValidateName("decisions")
+		} else if ce, ok := err.(*errors.CompositeError); ok {
+			return ce.ValidateName("decisions")
+		}
+		return err
+	}
+
+	return nil
+}
+
+func (m *AddSignalsRequestItem) validateMessage(formats strfmt.Registry) error {
+
+	if err := validate.Required("message", "body", m.Message); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *AddSignalsRequestItem) validateScenario(formats strfmt.Registry) error {
+
+	if err := validate.Required("scenario", "body", m.Scenario); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *AddSignalsRequestItem) validateScenarioHash(formats strfmt.Registry) error {
+
+	if err := validate.Required("scenario_hash", "body", m.ScenarioHash); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *AddSignalsRequestItem) validateScenarioVersion(formats strfmt.Registry) error {
+
+	if err := validate.Required("scenario_version", "body", m.ScenarioVersion); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *AddSignalsRequestItem) validateSource(formats strfmt.Registry) error {
+
+	if err := validate.Required("source", "body", m.Source); err != nil {
+		return err
+	}
+
+	if m.Source != nil {
+		if err := m.Source.Validate(formats); err != nil {
+			if ve, ok := err.(*errors.Validation); ok {
+				return ve.ValidateName("source")
+			} else if ce, ok := err.(*errors.CompositeError); ok {
+				return ce.ValidateName("source")
+			}
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (m *AddSignalsRequestItem) validateStartAt(formats strfmt.Registry) error {
+
+	if err := validate.Required("start_at", "body", m.StartAt); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *AddSignalsRequestItem) validateStopAt(formats strfmt.Registry) error {
+
+	if err := validate.Required("stop_at", "body", m.StopAt); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ContextValidate validate this add signals request item based on the context it is used
+func (m *AddSignalsRequestItem) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.contextValidateContext(ctx, formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.contextValidateDecisions(ctx, formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.contextValidateSource(ctx, formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *AddSignalsRequestItem) contextValidateContext(ctx context.Context, formats strfmt.Registry) error {
+
+	for i := 0; i < len(m.Context); i++ {
+
+		if m.Context[i] != nil {
+			if err := m.Context[i].ContextValidate(ctx, formats); err != nil {
+				if ve, ok := err.(*errors.Validation); ok {
+					return ve.ValidateName("context" + "." + strconv.Itoa(i))
+				} else if ce, ok := err.(*errors.CompositeError); ok {
+					return ce.ValidateName("context" + "." + strconv.Itoa(i))
+				}
+				return err
+			}
+		}
+
+	}
+
+	return nil
+}
+
+func (m *AddSignalsRequestItem) contextValidateDecisions(ctx context.Context, formats strfmt.Registry) error {
+
+	if err := m.Decisions.ContextValidate(ctx, formats); err != nil {
+		if ve, ok := err.(*errors.Validation); ok {
+			return ve.ValidateName("decisions")
+		} else if ce, ok := err.(*errors.CompositeError); ok {
+			return ce.ValidateName("decisions")
+		}
+		return err
+	}
+
+	return nil
+}
+
+func (m *AddSignalsRequestItem) contextValidateSource(ctx context.Context, formats strfmt.Registry) error {
+
+	if m.Source != nil {
+		if err := m.Source.ContextValidate(ctx, formats); err != nil {
+			if ve, ok := err.(*errors.Validation); ok {
+				return ve.ValidateName("source")
+			} else if ce, ok := err.(*errors.CompositeError); ok {
+				return ce.ValidateName("source")
+			}
+			return err
+		}
+	}
+
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *AddSignalsRequestItem) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *AddSignalsRequestItem) UnmarshalBinary(b []byte) error {
+	var res AddSignalsRequestItem
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}
+
+// AddSignalsRequestItemContextItems0 add signals request item context items0
+//
+// swagger:model AddSignalsRequestItemContextItems0
+type AddSignalsRequestItemContextItems0 struct {
+
+	// key
+	Key string `json:"key,omitempty"`
+
+	// value
+	Value string `json:"value,omitempty"`
+}
+
+// Validate validates this add signals request item context items0
+func (m *AddSignalsRequestItemContextItems0) Validate(formats strfmt.Registry) error {
+	return nil
+}
+
+// ContextValidate validates this add signals request item context items0 based on context it is used
+func (m *AddSignalsRequestItemContextItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *AddSignalsRequestItemContextItems0) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *AddSignalsRequestItemContextItems0) UnmarshalBinary(b []byte) error {
+	var res AddSignalsRequestItemContextItems0
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}

+ 73 - 0
pkg/modelscapi/add_signals_request_item_decisions.go

@@ -0,0 +1,73 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package modelscapi
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+	"strconv"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+)
+
+// AddSignalsRequestItemDecisions Decisions list
+//
+// swagger:model AddSignalsRequestItemDecisions
+type AddSignalsRequestItemDecisions []*AddSignalsRequestItemDecisionsItem
+
+// Validate validates this add signals request item decisions
+func (m AddSignalsRequestItemDecisions) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	for i := 0; i < len(m); i++ {
+		if swag.IsZero(m[i]) { // not required
+			continue
+		}
+
+		if m[i] != nil {
+			if err := m[i].Validate(formats); err != nil {
+				if ve, ok := err.(*errors.Validation); ok {
+					return ve.ValidateName(strconv.Itoa(i))
+				} else if ce, ok := err.(*errors.CompositeError); ok {
+					return ce.ValidateName(strconv.Itoa(i))
+				}
+				return err
+			}
+		}
+
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+// ContextValidate validate this add signals request item decisions based on the context it is used
+func (m AddSignalsRequestItemDecisions) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	var res []error
+
+	for i := 0; i < len(m); i++ {
+
+		if m[i] != nil {
+			if err := m[i].ContextValidate(ctx, formats); err != nil {
+				if ve, ok := err.(*errors.Validation); ok {
+					return ve.ValidateName(strconv.Itoa(i))
+				} else if ce, ok := err.(*errors.CompositeError); ok {
+					return ce.ValidateName(strconv.Itoa(i))
+				}
+				return err
+			}
+		}
+
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}

+ 179 - 0
pkg/modelscapi/add_signals_request_item_decisions_item.go

@@ -0,0 +1,179 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package modelscapi
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+	"github.com/go-openapi/validate"
+)
+
+// AddSignalsRequestItemDecisionsItem Decision
+//
+// swagger:model AddSignalsRequestItemDecisionsItem
+type AddSignalsRequestItemDecisionsItem struct {
+
+	// duration
+	// Required: true
+	Duration *string `json:"duration"`
+
+	// (only relevant for GET ops) the unique id
+	// Required: true
+	ID *int64 `json:"id"`
+
+	// the origin of the decision : cscli, crowdsec
+	// Required: true
+	Origin *string `json:"origin"`
+
+	// scenario
+	// Required: true
+	Scenario *string `json:"scenario"`
+
+	// the scope of decision : does it apply to an IP, a range, a username, etc
+	// Required: true
+	Scope *string `json:"scope"`
+
+	// simulated
+	Simulated bool `json:"simulated,omitempty"`
+
+	// the type of decision, might be 'ban', 'captcha' or something custom. Ignored when watcher (cscli/crowdsec) is pushing to APIL.
+	// Required: true
+	Type *string `json:"type"`
+
+	// until
+	Until string `json:"until,omitempty"`
+
+	// the value of the decision scope : an IP, a range, a username, etc
+	// Required: true
+	Value *string `json:"value"`
+}
+
+// Validate validates this add signals request item decisions item
+func (m *AddSignalsRequestItemDecisionsItem) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.validateDuration(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateID(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateOrigin(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateScenario(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateScope(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateType(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateValue(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *AddSignalsRequestItemDecisionsItem) validateDuration(formats strfmt.Registry) error {
+
+	if err := validate.Required("duration", "body", m.Duration); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *AddSignalsRequestItemDecisionsItem) validateID(formats strfmt.Registry) error {
+
+	if err := validate.Required("id", "body", m.ID); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *AddSignalsRequestItemDecisionsItem) validateOrigin(formats strfmt.Registry) error {
+
+	if err := validate.Required("origin", "body", m.Origin); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *AddSignalsRequestItemDecisionsItem) validateScenario(formats strfmt.Registry) error {
+
+	if err := validate.Required("scenario", "body", m.Scenario); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *AddSignalsRequestItemDecisionsItem) validateScope(formats strfmt.Registry) error {
+
+	if err := validate.Required("scope", "body", m.Scope); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *AddSignalsRequestItemDecisionsItem) validateType(formats strfmt.Registry) error {
+
+	if err := validate.Required("type", "body", m.Type); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *AddSignalsRequestItemDecisionsItem) validateValue(formats strfmt.Registry) error {
+
+	if err := validate.Required("value", "body", m.Value); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ContextValidate validates this add signals request item decisions item based on context it is used
+func (m *AddSignalsRequestItemDecisionsItem) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *AddSignalsRequestItemDecisionsItem) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *AddSignalsRequestItemDecisionsItem) UnmarshalBinary(b []byte) error {
+	var res AddSignalsRequestItemDecisionsItem
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}

+ 109 - 0
pkg/modelscapi/add_signals_request_item_source.go

@@ -0,0 +1,109 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package modelscapi
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+	"github.com/go-openapi/validate"
+)
+
+// AddSignalsRequestItemSource Source
+//
+// swagger:model AddSignalsRequestItemSource
+type AddSignalsRequestItemSource struct {
+
+	// provided as a convenience when the source is an IP
+	AsName string `json:"as_name,omitempty"`
+
+	// provided as a convenience when the source is an IP
+	AsNumber string `json:"as_number,omitempty"`
+
+	// cn
+	Cn string `json:"cn,omitempty"`
+
+	// provided as a convenience when the source is an IP
+	IP string `json:"ip,omitempty"`
+
+	// latitude
+	Latitude float32 `json:"latitude,omitempty"`
+
+	// longitude
+	Longitude float32 `json:"longitude,omitempty"`
+
+	// provided as a convenience when the source is an IP
+	Range string `json:"range,omitempty"`
+
+	// the scope of a source : ip,range,username,etc
+	// Required: true
+	Scope *string `json:"scope"`
+
+	// the value of a source : the ip, the range, the username,etc
+	// Required: true
+	Value *string `json:"value"`
+}
+
+// Validate validates this add signals request item source
+func (m *AddSignalsRequestItemSource) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.validateScope(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateValue(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *AddSignalsRequestItemSource) validateScope(formats strfmt.Registry) error {
+
+	if err := validate.Required("scope", "body", m.Scope); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *AddSignalsRequestItemSource) validateValue(formats strfmt.Registry) error {
+
+	if err := validate.Required("value", "body", m.Value); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ContextValidate validates this add signals request item source based on context it is used
+func (m *AddSignalsRequestItemSource) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *AddSignalsRequestItemSource) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *AddSignalsRequestItemSource) UnmarshalBinary(b []byte) error {
+	var res AddSignalsRequestItemSource
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}

+ 139 - 0
pkg/modelscapi/blocklist_link.go

@@ -0,0 +1,139 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package modelscapi
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+	"github.com/go-openapi/validate"
+)
+
+// BlocklistLink blocklist link
+//
+// swagger:model BlocklistLink
+type BlocklistLink struct {
+
+	// duration
+	// Required: true
+	Duration *string `json:"duration"`
+
+	// the name of the blocklist
+	// Required: true
+	Name *string `json:"name"`
+
+	// the remediation that should be used for the blocklist
+	// Required: true
+	Remediation *string `json:"remediation"`
+
+	// the scope of decisions in the blocklist
+	// Required: true
+	Scope *string `json:"scope"`
+
+	// the url from which the blocklist content can be downloaded
+	// Required: true
+	URL *string `json:"url"`
+}
+
+// Validate validates this blocklist link
+func (m *BlocklistLink) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.validateDuration(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateName(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateRemediation(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateScope(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateURL(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *BlocklistLink) validateDuration(formats strfmt.Registry) error {
+
+	if err := validate.Required("duration", "body", m.Duration); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *BlocklistLink) validateName(formats strfmt.Registry) error {
+
+	if err := validate.Required("name", "body", m.Name); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *BlocklistLink) validateRemediation(formats strfmt.Registry) error {
+
+	if err := validate.Required("remediation", "body", m.Remediation); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *BlocklistLink) validateScope(formats strfmt.Registry) error {
+
+	if err := validate.Required("scope", "body", m.Scope); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *BlocklistLink) validateURL(formats strfmt.Registry) error {
+
+	if err := validate.Required("url", "body", m.URL); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ContextValidate validates this blocklist link based on context it is used
+func (m *BlocklistLink) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *BlocklistLink) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *BlocklistLink) UnmarshalBinary(b []byte) error {
+	var res BlocklistLink
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}

+ 67 - 0
pkg/modelscapi/decisions_delete_request.go

@@ -0,0 +1,67 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package modelscapi
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+	"strconv"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/strfmt"
+)
+
+// DecisionsDeleteRequest delete decisions
+//
+// delete decision model
+//
+// swagger:model DecisionsDeleteRequest
+type DecisionsDeleteRequest []DecisionsDeleteRequestItem
+
+// Validate validates this decisions delete request
+func (m DecisionsDeleteRequest) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	for i := 0; i < len(m); i++ {
+
+		if err := m[i].Validate(formats); err != nil {
+			if ve, ok := err.(*errors.Validation); ok {
+				return ve.ValidateName(strconv.Itoa(i))
+			} else if ce, ok := err.(*errors.CompositeError); ok {
+				return ce.ValidateName(strconv.Itoa(i))
+			}
+			return err
+		}
+
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+// ContextValidate validate this decisions delete request based on the context it is used
+func (m DecisionsDeleteRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	var res []error
+
+	for i := 0; i < len(m); i++ {
+
+		if err := m[i].ContextValidate(ctx, formats); err != nil {
+			if ve, ok := err.(*errors.Validation); ok {
+				return ve.ValidateName(strconv.Itoa(i))
+			} else if ce, ok := err.(*errors.CompositeError); ok {
+				return ce.ValidateName(strconv.Itoa(i))
+			}
+			return err
+		}
+
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}

+ 27 - 0
pkg/modelscapi/decisions_delete_request_item.go

@@ -0,0 +1,27 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package modelscapi
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+
+	"github.com/go-openapi/strfmt"
+)
+
+// DecisionsDeleteRequestItem decisionsIDs
+//
+// swagger:model DecisionsDeleteRequestItem
+type DecisionsDeleteRequestItem string
+
+// Validate validates this decisions delete request item
+func (m DecisionsDeleteRequestItem) Validate(formats strfmt.Registry) error {
+	return nil
+}
+
+// ContextValidate validates this decisions delete request item based on context it is used
+func (m DecisionsDeleteRequestItem) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	return nil
+}

+ 75 - 0
pkg/modelscapi/decisions_sync_request.go

@@ -0,0 +1,75 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package modelscapi
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+	"strconv"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+)
+
+// DecisionsSyncRequest sync decisions request
+//
+// sync decision model
+//
+// swagger:model DecisionsSyncRequest
+type DecisionsSyncRequest []*DecisionsSyncRequestItem
+
+// Validate validates this decisions sync request
+func (m DecisionsSyncRequest) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	for i := 0; i < len(m); i++ {
+		if swag.IsZero(m[i]) { // not required
+			continue
+		}
+
+		if m[i] != nil {
+			if err := m[i].Validate(formats); err != nil {
+				if ve, ok := err.(*errors.Validation); ok {
+					return ve.ValidateName(strconv.Itoa(i))
+				} else if ce, ok := err.(*errors.CompositeError); ok {
+					return ce.ValidateName(strconv.Itoa(i))
+				}
+				return err
+			}
+		}
+
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+// ContextValidate validate this decisions sync request based on the context it is used
+func (m DecisionsSyncRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	var res []error
+
+	for i := 0; i < len(m); i++ {
+
+		if m[i] != nil {
+			if err := m[i].ContextValidate(ctx, formats); err != nil {
+				if ve, ok := err.(*errors.Validation); ok {
+					return ve.ValidateName(strconv.Itoa(i))
+				} else if ce, ok := err.(*errors.CompositeError); ok {
+					return ce.ValidateName(strconv.Itoa(i))
+				}
+				return err
+			}
+		}
+
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}

+ 263 - 0
pkg/modelscapi/decisions_sync_request_item.go

@@ -0,0 +1,263 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package modelscapi
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+	"github.com/go-openapi/validate"
+)
+
+// DecisionsSyncRequestItem Signal
+//
+// swagger:model DecisionsSyncRequestItem
+type DecisionsSyncRequestItem struct {
+
+	// alert id
+	AlertID int64 `json:"alert_id,omitempty"`
+
+	// created at
+	CreatedAt string `json:"created_at,omitempty"`
+
+	// decisions
+	Decisions DecisionsSyncRequestItemDecisions `json:"decisions,omitempty"`
+
+	// machine id
+	MachineID string `json:"machine_id,omitempty"`
+
+	// a human readable message
+	// Required: true
+	Message *string `json:"message"`
+
+	// scenario
+	// Required: true
+	Scenario *string `json:"scenario"`
+
+	// scenario hash
+	// Required: true
+	ScenarioHash *string `json:"scenario_hash"`
+
+	// scenario trust
+	ScenarioTrust string `json:"scenario_trust,omitempty"`
+
+	// scenario version
+	// Required: true
+	ScenarioVersion *string `json:"scenario_version"`
+
+	// source
+	// Required: true
+	Source *DecisionsSyncRequestItemSource `json:"source"`
+
+	// start at
+	// Required: true
+	StartAt *string `json:"start_at"`
+
+	// stop at
+	// Required: true
+	StopAt *string `json:"stop_at"`
+}
+
+// Validate validates this decisions sync request item
+func (m *DecisionsSyncRequestItem) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.validateDecisions(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateMessage(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateScenario(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateScenarioHash(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateScenarioVersion(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateSource(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateStartAt(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateStopAt(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *DecisionsSyncRequestItem) validateDecisions(formats strfmt.Registry) error {
+	if swag.IsZero(m.Decisions) { // not required
+		return nil
+	}
+
+	if err := m.Decisions.Validate(formats); err != nil {
+		if ve, ok := err.(*errors.Validation); ok {
+			return ve.ValidateName("decisions")
+		} else if ce, ok := err.(*errors.CompositeError); ok {
+			return ce.ValidateName("decisions")
+		}
+		return err
+	}
+
+	return nil
+}
+
+func (m *DecisionsSyncRequestItem) validateMessage(formats strfmt.Registry) error {
+
+	if err := validate.Required("message", "body", m.Message); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *DecisionsSyncRequestItem) validateScenario(formats strfmt.Registry) error {
+
+	if err := validate.Required("scenario", "body", m.Scenario); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *DecisionsSyncRequestItem) validateScenarioHash(formats strfmt.Registry) error {
+
+	if err := validate.Required("scenario_hash", "body", m.ScenarioHash); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *DecisionsSyncRequestItem) validateScenarioVersion(formats strfmt.Registry) error {
+
+	if err := validate.Required("scenario_version", "body", m.ScenarioVersion); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *DecisionsSyncRequestItem) validateSource(formats strfmt.Registry) error {
+
+	if err := validate.Required("source", "body", m.Source); err != nil {
+		return err
+	}
+
+	if m.Source != nil {
+		if err := m.Source.Validate(formats); err != nil {
+			if ve, ok := err.(*errors.Validation); ok {
+				return ve.ValidateName("source")
+			} else if ce, ok := err.(*errors.CompositeError); ok {
+				return ce.ValidateName("source")
+			}
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (m *DecisionsSyncRequestItem) validateStartAt(formats strfmt.Registry) error {
+
+	if err := validate.Required("start_at", "body", m.StartAt); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *DecisionsSyncRequestItem) validateStopAt(formats strfmt.Registry) error {
+
+	if err := validate.Required("stop_at", "body", m.StopAt); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ContextValidate validate this decisions sync request item based on the context it is used
+func (m *DecisionsSyncRequestItem) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.contextValidateDecisions(ctx, formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.contextValidateSource(ctx, formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *DecisionsSyncRequestItem) contextValidateDecisions(ctx context.Context, formats strfmt.Registry) error {
+
+	if err := m.Decisions.ContextValidate(ctx, formats); err != nil {
+		if ve, ok := err.(*errors.Validation); ok {
+			return ve.ValidateName("decisions")
+		} else if ce, ok := err.(*errors.CompositeError); ok {
+			return ce.ValidateName("decisions")
+		}
+		return err
+	}
+
+	return nil
+}
+
+func (m *DecisionsSyncRequestItem) contextValidateSource(ctx context.Context, formats strfmt.Registry) error {
+
+	if m.Source != nil {
+		if err := m.Source.ContextValidate(ctx, formats); err != nil {
+			if ve, ok := err.(*errors.Validation); ok {
+				return ve.ValidateName("source")
+			} else if ce, ok := err.(*errors.CompositeError); ok {
+				return ce.ValidateName("source")
+			}
+			return err
+		}
+	}
+
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *DecisionsSyncRequestItem) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *DecisionsSyncRequestItem) UnmarshalBinary(b []byte) error {
+	var res DecisionsSyncRequestItem
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}

+ 73 - 0
pkg/modelscapi/decisions_sync_request_item_decisions.go

@@ -0,0 +1,73 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package modelscapi
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+	"strconv"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+)
+
+// DecisionsSyncRequestItemDecisions Decisions list
+//
+// swagger:model DecisionsSyncRequestItemDecisions
+type DecisionsSyncRequestItemDecisions []*DecisionsSyncRequestItemDecisionsItem
+
+// Validate validates this decisions sync request item decisions
+func (m DecisionsSyncRequestItemDecisions) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	for i := 0; i < len(m); i++ {
+		if swag.IsZero(m[i]) { // not required
+			continue
+		}
+
+		if m[i] != nil {
+			if err := m[i].Validate(formats); err != nil {
+				if ve, ok := err.(*errors.Validation); ok {
+					return ve.ValidateName(strconv.Itoa(i))
+				} else if ce, ok := err.(*errors.CompositeError); ok {
+					return ce.ValidateName(strconv.Itoa(i))
+				}
+				return err
+			}
+		}
+
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+// ContextValidate validate this decisions sync request item decisions based on the context it is used
+func (m DecisionsSyncRequestItemDecisions) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	var res []error
+
+	for i := 0; i < len(m); i++ {
+
+		if m[i] != nil {
+			if err := m[i].ContextValidate(ctx, formats); err != nil {
+				if ve, ok := err.(*errors.Validation); ok {
+					return ve.ValidateName(strconv.Itoa(i))
+				} else if ce, ok := err.(*errors.CompositeError); ok {
+					return ce.ValidateName(strconv.Itoa(i))
+				}
+				return err
+			}
+		}
+
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}

+ 179 - 0
pkg/modelscapi/decisions_sync_request_item_decisions_item.go

@@ -0,0 +1,179 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package modelscapi
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+	"github.com/go-openapi/validate"
+)
+
+// DecisionsSyncRequestItemDecisionsItem Decision
+//
+// swagger:model DecisionsSyncRequestItemDecisionsItem
+type DecisionsSyncRequestItemDecisionsItem struct {
+
+	// duration
+	// Required: true
+	Duration *string `json:"duration"`
+
+	// (only relevant for GET ops) the unique id
+	// Required: true
+	ID *int64 `json:"id"`
+
+	// the origin of the decision : cscli, crowdsec
+	// Required: true
+	Origin *string `json:"origin"`
+
+	// scenario
+	// Required: true
+	Scenario *string `json:"scenario"`
+
+	// the scope of decision : does it apply to an IP, a range, a username, etc
+	// Required: true
+	Scope *string `json:"scope"`
+
+	// simulated
+	Simulated bool `json:"simulated,omitempty"`
+
+	// the type of decision, might be 'ban', 'captcha' or something custom. Ignored when watcher (cscli/crowdsec) is pushing to APIL.
+	// Required: true
+	Type *string `json:"type"`
+
+	// until
+	Until string `json:"until,omitempty"`
+
+	// the value of the decision scope : an IP, a range, a username, etc
+	// Required: true
+	Value *string `json:"value"`
+}
+
+// Validate validates this decisions sync request item decisions item
+func (m *DecisionsSyncRequestItemDecisionsItem) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.validateDuration(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateID(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateOrigin(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateScenario(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateScope(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateType(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateValue(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *DecisionsSyncRequestItemDecisionsItem) validateDuration(formats strfmt.Registry) error {
+
+	if err := validate.Required("duration", "body", m.Duration); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *DecisionsSyncRequestItemDecisionsItem) validateID(formats strfmt.Registry) error {
+
+	if err := validate.Required("id", "body", m.ID); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *DecisionsSyncRequestItemDecisionsItem) validateOrigin(formats strfmt.Registry) error {
+
+	if err := validate.Required("origin", "body", m.Origin); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *DecisionsSyncRequestItemDecisionsItem) validateScenario(formats strfmt.Registry) error {
+
+	if err := validate.Required("scenario", "body", m.Scenario); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *DecisionsSyncRequestItemDecisionsItem) validateScope(formats strfmt.Registry) error {
+
+	if err := validate.Required("scope", "body", m.Scope); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *DecisionsSyncRequestItemDecisionsItem) validateType(formats strfmt.Registry) error {
+
+	if err := validate.Required("type", "body", m.Type); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *DecisionsSyncRequestItemDecisionsItem) validateValue(formats strfmt.Registry) error {
+
+	if err := validate.Required("value", "body", m.Value); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ContextValidate validates this decisions sync request item decisions item based on context it is used
+func (m *DecisionsSyncRequestItemDecisionsItem) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *DecisionsSyncRequestItemDecisionsItem) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *DecisionsSyncRequestItemDecisionsItem) UnmarshalBinary(b []byte) error {
+	var res DecisionsSyncRequestItemDecisionsItem
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}

+ 109 - 0
pkg/modelscapi/decisions_sync_request_item_source.go

@@ -0,0 +1,109 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package modelscapi
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+	"github.com/go-openapi/validate"
+)
+
+// DecisionsSyncRequestItemSource Source
+//
+// swagger:model DecisionsSyncRequestItemSource
+type DecisionsSyncRequestItemSource struct {
+
+	// provided as a convenience when the source is an IP
+	AsName string `json:"as_name,omitempty"`
+
+	// provided as a convenience when the source is an IP
+	AsNumber string `json:"as_number,omitempty"`
+
+	// cn
+	Cn string `json:"cn,omitempty"`
+
+	// provided as a convenience when the source is an IP
+	IP string `json:"ip,omitempty"`
+
+	// latitude
+	Latitude float32 `json:"latitude,omitempty"`
+
+	// longitude
+	Longitude float32 `json:"longitude,omitempty"`
+
+	// provided as a convenience when the source is an IP
+	Range string `json:"range,omitempty"`
+
+	// the scope of a source : ip,range,username,etc
+	// Required: true
+	Scope *string `json:"scope"`
+
+	// the value of a source : the ip, the range, the username,etc
+	// Required: true
+	Value *string `json:"value"`
+}
+
+// Validate validates this decisions sync request item source
+func (m *DecisionsSyncRequestItemSource) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.validateScope(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateValue(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *DecisionsSyncRequestItemSource) validateScope(formats strfmt.Registry) error {
+
+	if err := validate.Required("scope", "body", m.Scope); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *DecisionsSyncRequestItemSource) validateValue(formats strfmt.Registry) error {
+
+	if err := validate.Required("value", "body", m.Value); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ContextValidate validates this decisions sync request item source based on context it is used
+func (m *DecisionsSyncRequestItemSource) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *DecisionsSyncRequestItemSource) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *DecisionsSyncRequestItemSource) UnmarshalBinary(b []byte) error {
+	var res DecisionsSyncRequestItemSource
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}

+ 87 - 0
pkg/modelscapi/enroll_request.go

@@ -0,0 +1,87 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package modelscapi
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+	"github.com/go-openapi/validate"
+)
+
+// EnrollRequest enroll request
+//
+// enroll request model
+//
+// swagger:model EnrollRequest
+type EnrollRequest struct {
+
+	// attachment_key is generated in your crowdsec backoffice account and allows you to enroll your machines to your BO account
+	// Required: true
+	// Pattern: ^[a-zA-Z0-9]+$
+	AttachmentKey *string `json:"attachment_key"`
+
+	// The name that will be display in the console for the instance
+	Name string `json:"name,omitempty"`
+
+	// To force enroll the instance
+	Overwrite bool `json:"overwrite,omitempty"`
+
+	// Tags to apply on the console for the instance
+	Tags []string `json:"tags"`
+}
+
+// Validate validates this enroll request
+func (m *EnrollRequest) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.validateAttachmentKey(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *EnrollRequest) validateAttachmentKey(formats strfmt.Registry) error {
+
+	if err := validate.Required("attachment_key", "body", m.AttachmentKey); err != nil {
+		return err
+	}
+
+	if err := validate.Pattern("attachment_key", "body", *m.AttachmentKey, `^[a-zA-Z0-9]+$`); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ContextValidate validates this enroll request based on context it is used
+func (m *EnrollRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *EnrollRequest) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *EnrollRequest) UnmarshalBinary(b []byte) error {
+	var res EnrollRequest
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}

+ 76 - 0
pkg/modelscapi/error_response.go

@@ -0,0 +1,76 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package modelscapi
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+	"github.com/go-openapi/validate"
+)
+
+// ErrorResponse error response
+//
+// error response return by the API
+//
+// swagger:model ErrorResponse
+type ErrorResponse struct {
+
+	// more detail on individual errors
+	Errors string `json:"errors,omitempty"`
+
+	// Error message
+	// Required: true
+	Message *string `json:"message"`
+}
+
+// Validate validates this error response
+func (m *ErrorResponse) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.validateMessage(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *ErrorResponse) validateMessage(formats strfmt.Registry) error {
+
+	if err := validate.Required("message", "body", m.Message); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ContextValidate validates this error response based on context it is used
+func (m *ErrorResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *ErrorResponse) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *ErrorResponse) UnmarshalBinary(b []byte) error {
+	var res ErrorResponse
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}

+ 190 - 0
pkg/modelscapi/get_decisions_stream_response.go

@@ -0,0 +1,190 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package modelscapi
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+)
+
+// GetDecisionsStreamResponse get decisions stream response
+//
+// get decision response model
+//
+// swagger:model GetDecisionsStreamResponse
+type GetDecisionsStreamResponse struct {
+
+	// deleted
+	Deleted GetDecisionsStreamResponseDeleted `json:"deleted,omitempty"`
+
+	// links
+	Links *GetDecisionsStreamResponseLinks `json:"links,omitempty"`
+
+	// new
+	New GetDecisionsStreamResponseNew `json:"new,omitempty"`
+}
+
+// Validate validates this get decisions stream response
+func (m *GetDecisionsStreamResponse) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.validateDeleted(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateLinks(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateNew(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *GetDecisionsStreamResponse) validateDeleted(formats strfmt.Registry) error {
+	if swag.IsZero(m.Deleted) { // not required
+		return nil
+	}
+
+	if err := m.Deleted.Validate(formats); err != nil {
+		if ve, ok := err.(*errors.Validation); ok {
+			return ve.ValidateName("deleted")
+		} else if ce, ok := err.(*errors.CompositeError); ok {
+			return ce.ValidateName("deleted")
+		}
+		return err
+	}
+
+	return nil
+}
+
+func (m *GetDecisionsStreamResponse) validateLinks(formats strfmt.Registry) error {
+	if swag.IsZero(m.Links) { // not required
+		return nil
+	}
+
+	if m.Links != nil {
+		if err := m.Links.Validate(formats); err != nil {
+			if ve, ok := err.(*errors.Validation); ok {
+				return ve.ValidateName("links")
+			} else if ce, ok := err.(*errors.CompositeError); ok {
+				return ce.ValidateName("links")
+			}
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (m *GetDecisionsStreamResponse) validateNew(formats strfmt.Registry) error {
+	if swag.IsZero(m.New) { // not required
+		return nil
+	}
+
+	if err := m.New.Validate(formats); err != nil {
+		if ve, ok := err.(*errors.Validation); ok {
+			return ve.ValidateName("new")
+		} else if ce, ok := err.(*errors.CompositeError); ok {
+			return ce.ValidateName("new")
+		}
+		return err
+	}
+
+	return nil
+}
+
+// ContextValidate validate this get decisions stream response based on the context it is used
+func (m *GetDecisionsStreamResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.contextValidateDeleted(ctx, formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.contextValidateLinks(ctx, formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.contextValidateNew(ctx, formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *GetDecisionsStreamResponse) contextValidateDeleted(ctx context.Context, formats strfmt.Registry) error {
+
+	if err := m.Deleted.ContextValidate(ctx, formats); err != nil {
+		if ve, ok := err.(*errors.Validation); ok {
+			return ve.ValidateName("deleted")
+		} else if ce, ok := err.(*errors.CompositeError); ok {
+			return ce.ValidateName("deleted")
+		}
+		return err
+	}
+
+	return nil
+}
+
+func (m *GetDecisionsStreamResponse) contextValidateLinks(ctx context.Context, formats strfmt.Registry) error {
+
+	if m.Links != nil {
+		if err := m.Links.ContextValidate(ctx, formats); err != nil {
+			if ve, ok := err.(*errors.Validation); ok {
+				return ve.ValidateName("links")
+			} else if ce, ok := err.(*errors.CompositeError); ok {
+				return ce.ValidateName("links")
+			}
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (m *GetDecisionsStreamResponse) contextValidateNew(ctx context.Context, formats strfmt.Registry) error {
+
+	if err := m.New.ContextValidate(ctx, formats); err != nil {
+		if ve, ok := err.(*errors.Validation); ok {
+			return ve.ValidateName("new")
+		} else if ce, ok := err.(*errors.CompositeError); ok {
+			return ce.ValidateName("new")
+		}
+		return err
+	}
+
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *GetDecisionsStreamResponse) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *GetDecisionsStreamResponse) UnmarshalBinary(b []byte) error {
+	var res GetDecisionsStreamResponse
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}

+ 73 - 0
pkg/modelscapi/get_decisions_stream_response_deleted.go

@@ -0,0 +1,73 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package modelscapi
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+	"strconv"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+)
+
+// GetDecisionsStreamResponseDeleted Decisions list
+//
+// swagger:model GetDecisionsStreamResponseDeleted
+type GetDecisionsStreamResponseDeleted []*GetDecisionsStreamResponseDeletedItem
+
+// Validate validates this get decisions stream response deleted
+func (m GetDecisionsStreamResponseDeleted) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	for i := 0; i < len(m); i++ {
+		if swag.IsZero(m[i]) { // not required
+			continue
+		}
+
+		if m[i] != nil {
+			if err := m[i].Validate(formats); err != nil {
+				if ve, ok := err.(*errors.Validation); ok {
+					return ve.ValidateName(strconv.Itoa(i))
+				} else if ce, ok := err.(*errors.CompositeError); ok {
+					return ce.ValidateName(strconv.Itoa(i))
+				}
+				return err
+			}
+		}
+
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+// ContextValidate validate this get decisions stream response deleted based on the context it is used
+func (m GetDecisionsStreamResponseDeleted) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	var res []error
+
+	for i := 0; i < len(m); i++ {
+
+		if m[i] != nil {
+			if err := m[i].ContextValidate(ctx, formats); err != nil {
+				if ve, ok := err.(*errors.Validation); ok {
+					return ve.ValidateName(strconv.Itoa(i))
+				} else if ce, ok := err.(*errors.CompositeError); ok {
+					return ce.ValidateName(strconv.Itoa(i))
+				}
+				return err
+			}
+		}
+
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}

+ 88 - 0
pkg/modelscapi/get_decisions_stream_response_deleted_item.go

@@ -0,0 +1,88 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package modelscapi
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+	"github.com/go-openapi/validate"
+)
+
+// GetDecisionsStreamResponseDeletedItem get decisions stream response deleted item
+//
+// swagger:model GetDecisionsStreamResponseDeletedItem
+type GetDecisionsStreamResponseDeletedItem struct {
+
+	// decisions
+	// Required: true
+	Decisions []string `json:"decisions"`
+
+	// the scope of decision : does it apply to an IP, a range, a username, etc
+	// Required: true
+	Scope *string `json:"scope"`
+}
+
+// Validate validates this get decisions stream response deleted item
+func (m *GetDecisionsStreamResponseDeletedItem) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.validateDecisions(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateScope(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *GetDecisionsStreamResponseDeletedItem) validateDecisions(formats strfmt.Registry) error {
+
+	if err := validate.Required("decisions", "body", m.Decisions); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *GetDecisionsStreamResponseDeletedItem) validateScope(formats strfmt.Registry) error {
+
+	if err := validate.Required("scope", "body", m.Scope); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ContextValidate validates this get decisions stream response deleted item based on context it is used
+func (m *GetDecisionsStreamResponseDeletedItem) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *GetDecisionsStreamResponseDeletedItem) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *GetDecisionsStreamResponseDeletedItem) UnmarshalBinary(b []byte) error {
+	var res GetDecisionsStreamResponseDeletedItem
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}

+ 116 - 0
pkg/modelscapi/get_decisions_stream_response_links.go

@@ -0,0 +1,116 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package modelscapi
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+	"strconv"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+)
+
+// GetDecisionsStreamResponseLinks Decisions list
+//
+// swagger:model GetDecisionsStreamResponseLinks
+type GetDecisionsStreamResponseLinks struct {
+
+	// blocklists
+	Blocklists []*BlocklistLink `json:"blocklists"`
+}
+
+// Validate validates this get decisions stream response links
+func (m *GetDecisionsStreamResponseLinks) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.validateBlocklists(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *GetDecisionsStreamResponseLinks) validateBlocklists(formats strfmt.Registry) error {
+	if swag.IsZero(m.Blocklists) { // not required
+		return nil
+	}
+
+	for i := 0; i < len(m.Blocklists); i++ {
+		if swag.IsZero(m.Blocklists[i]) { // not required
+			continue
+		}
+
+		if m.Blocklists[i] != nil {
+			if err := m.Blocklists[i].Validate(formats); err != nil {
+				if ve, ok := err.(*errors.Validation); ok {
+					return ve.ValidateName("blocklists" + "." + strconv.Itoa(i))
+				} else if ce, ok := err.(*errors.CompositeError); ok {
+					return ce.ValidateName("blocklists" + "." + strconv.Itoa(i))
+				}
+				return err
+			}
+		}
+
+	}
+
+	return nil
+}
+
+// ContextValidate validate this get decisions stream response links based on the context it is used
+func (m *GetDecisionsStreamResponseLinks) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.contextValidateBlocklists(ctx, formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *GetDecisionsStreamResponseLinks) contextValidateBlocklists(ctx context.Context, formats strfmt.Registry) error {
+
+	for i := 0; i < len(m.Blocklists); i++ {
+
+		if m.Blocklists[i] != nil {
+			if err := m.Blocklists[i].ContextValidate(ctx, formats); err != nil {
+				if ve, ok := err.(*errors.Validation); ok {
+					return ve.ValidateName("blocklists" + "." + strconv.Itoa(i))
+				} else if ce, ok := err.(*errors.CompositeError); ok {
+					return ce.ValidateName("blocklists" + "." + strconv.Itoa(i))
+				}
+				return err
+			}
+		}
+
+	}
+
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *GetDecisionsStreamResponseLinks) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *GetDecisionsStreamResponseLinks) UnmarshalBinary(b []byte) error {
+	var res GetDecisionsStreamResponseLinks
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}

+ 73 - 0
pkg/modelscapi/get_decisions_stream_response_new.go

@@ -0,0 +1,73 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package modelscapi
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+	"strconv"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+)
+
+// GetDecisionsStreamResponseNew Decisions list
+//
+// swagger:model GetDecisionsStreamResponseNew
+type GetDecisionsStreamResponseNew []*GetDecisionsStreamResponseNewItem
+
+// Validate validates this get decisions stream response new
+func (m GetDecisionsStreamResponseNew) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	for i := 0; i < len(m); i++ {
+		if swag.IsZero(m[i]) { // not required
+			continue
+		}
+
+		if m[i] != nil {
+			if err := m[i].Validate(formats); err != nil {
+				if ve, ok := err.(*errors.Validation); ok {
+					return ve.ValidateName(strconv.Itoa(i))
+				} else if ce, ok := err.(*errors.CompositeError); ok {
+					return ce.ValidateName(strconv.Itoa(i))
+				}
+				return err
+			}
+		}
+
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+// ContextValidate validate this get decisions stream response new based on the context it is used
+func (m GetDecisionsStreamResponseNew) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	var res []error
+
+	for i := 0; i < len(m); i++ {
+
+		if m[i] != nil {
+			if err := m[i].ContextValidate(ctx, formats); err != nil {
+				if ve, ok := err.(*errors.Validation); ok {
+					return ve.ValidateName(strconv.Itoa(i))
+				} else if ce, ok := err.(*errors.CompositeError); ok {
+					return ce.ValidateName(strconv.Itoa(i))
+				}
+				return err
+			}
+		}
+
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}

+ 226 - 0
pkg/modelscapi/get_decisions_stream_response_new_item.go

@@ -0,0 +1,226 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package modelscapi
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+	"strconv"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+	"github.com/go-openapi/validate"
+)
+
+// GetDecisionsStreamResponseNewItem New Decisions
+//
+// swagger:model GetDecisionsStreamResponseNewItem
+type GetDecisionsStreamResponseNewItem struct {
+
+	// decisions
+	// Required: true
+	Decisions []*GetDecisionsStreamResponseNewItemDecisionsItems0 `json:"decisions"`
+
+	// scenario
+	// Required: true
+	Scenario *string `json:"scenario"`
+
+	// the scope of decision : does it apply to an IP, a range, a username, etc
+	// Required: true
+	Scope *string `json:"scope"`
+}
+
+// Validate validates this get decisions stream response new item
+func (m *GetDecisionsStreamResponseNewItem) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.validateDecisions(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateScenario(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateScope(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *GetDecisionsStreamResponseNewItem) validateDecisions(formats strfmt.Registry) error {
+
+	if err := validate.Required("decisions", "body", m.Decisions); err != nil {
+		return err
+	}
+
+	for i := 0; i < len(m.Decisions); i++ {
+		if swag.IsZero(m.Decisions[i]) { // not required
+			continue
+		}
+
+		if m.Decisions[i] != nil {
+			if err := m.Decisions[i].Validate(formats); err != nil {
+				if ve, ok := err.(*errors.Validation); ok {
+					return ve.ValidateName("decisions" + "." + strconv.Itoa(i))
+				} else if ce, ok := err.(*errors.CompositeError); ok {
+					return ce.ValidateName("decisions" + "." + strconv.Itoa(i))
+				}
+				return err
+			}
+		}
+
+	}
+
+	return nil
+}
+
+func (m *GetDecisionsStreamResponseNewItem) validateScenario(formats strfmt.Registry) error {
+
+	if err := validate.Required("scenario", "body", m.Scenario); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *GetDecisionsStreamResponseNewItem) validateScope(formats strfmt.Registry) error {
+
+	if err := validate.Required("scope", "body", m.Scope); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ContextValidate validate this get decisions stream response new item based on the context it is used
+func (m *GetDecisionsStreamResponseNewItem) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.contextValidateDecisions(ctx, formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *GetDecisionsStreamResponseNewItem) contextValidateDecisions(ctx context.Context, formats strfmt.Registry) error {
+
+	for i := 0; i < len(m.Decisions); i++ {
+
+		if m.Decisions[i] != nil {
+			if err := m.Decisions[i].ContextValidate(ctx, formats); err != nil {
+				if ve, ok := err.(*errors.Validation); ok {
+					return ve.ValidateName("decisions" + "." + strconv.Itoa(i))
+				} else if ce, ok := err.(*errors.CompositeError); ok {
+					return ce.ValidateName("decisions" + "." + strconv.Itoa(i))
+				}
+				return err
+			}
+		}
+
+	}
+
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *GetDecisionsStreamResponseNewItem) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *GetDecisionsStreamResponseNewItem) UnmarshalBinary(b []byte) error {
+	var res GetDecisionsStreamResponseNewItem
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}
+
+// GetDecisionsStreamResponseNewItemDecisionsItems0 get decisions stream response new item decisions items0
+//
+// swagger:model GetDecisionsStreamResponseNewItemDecisionsItems0
+type GetDecisionsStreamResponseNewItemDecisionsItems0 struct {
+
+	// duration
+	// Required: true
+	Duration *string `json:"duration"`
+
+	// the value of the decision scope : an IP, a range, a username, etc
+	// Required: true
+	Value *string `json:"value"`
+}
+
+// Validate validates this get decisions stream response new item decisions items0
+func (m *GetDecisionsStreamResponseNewItemDecisionsItems0) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.validateDuration(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateValue(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *GetDecisionsStreamResponseNewItemDecisionsItems0) validateDuration(formats strfmt.Registry) error {
+
+	if err := validate.Required("duration", "body", m.Duration); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *GetDecisionsStreamResponseNewItemDecisionsItems0) validateValue(formats strfmt.Registry) error {
+
+	if err := validate.Required("value", "body", m.Value); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ContextValidate validates this get decisions stream response new item decisions items0 based on context it is used
+func (m *GetDecisionsStreamResponseNewItemDecisionsItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *GetDecisionsStreamResponseNewItemDecisionsItems0) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *GetDecisionsStreamResponseNewItemDecisionsItems0) UnmarshalBinary(b []byte) error {
+	var res GetDecisionsStreamResponseNewItemDecisionsItems0
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}

+ 108 - 0
pkg/modelscapi/login_request.go

@@ -0,0 +1,108 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package modelscapi
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+	"github.com/go-openapi/validate"
+)
+
+// LoginRequest login request
+//
+// # Login request model
+//
+// swagger:model LoginRequest
+type LoginRequest struct {
+
+	// machine_id is a (username) generated by crowdsec
+	// Required: true
+	// Max Length: 48
+	// Min Length: 48
+	// Pattern: ^[a-zA-Z0-9]+$
+	MachineID *string `json:"machine_id"`
+
+	// Password, should respect the password policy (link to add)
+	// Required: true
+	Password *string `json:"password"`
+
+	// all scenarios installed
+	Scenarios []string `json:"scenarios"`
+}
+
+// Validate validates this login request
+func (m *LoginRequest) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.validateMachineID(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validatePassword(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *LoginRequest) validateMachineID(formats strfmt.Registry) error {
+
+	if err := validate.Required("machine_id", "body", m.MachineID); err != nil {
+		return err
+	}
+
+	if err := validate.MinLength("machine_id", "body", *m.MachineID, 48); err != nil {
+		return err
+	}
+
+	if err := validate.MaxLength("machine_id", "body", *m.MachineID, 48); err != nil {
+		return err
+	}
+
+	if err := validate.Pattern("machine_id", "body", *m.MachineID, `^[a-zA-Z0-9]+$`); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *LoginRequest) validatePassword(formats strfmt.Registry) error {
+
+	if err := validate.Required("password", "body", m.Password); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ContextValidate validates this login request based on context it is used
+func (m *LoginRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *LoginRequest) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *LoginRequest) UnmarshalBinary(b []byte) error {
+	var res LoginRequest
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}

+ 58 - 0
pkg/modelscapi/login_response.go

@@ -0,0 +1,58 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package modelscapi
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+)
+
+// LoginResponse login response
+//
+// # Login request model
+//
+// swagger:model LoginResponse
+type LoginResponse struct {
+
+	// code
+	Code int64 `json:"code,omitempty"`
+
+	// expire
+	Expire string `json:"expire,omitempty"`
+
+	// token
+	Token string `json:"token,omitempty"`
+}
+
+// Validate validates this login response
+func (m *LoginResponse) Validate(formats strfmt.Registry) error {
+	return nil
+}
+
+// ContextValidate validates this login response based on context it is used
+func (m *LoginResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *LoginResponse) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *LoginResponse) UnmarshalBinary(b []byte) error {
+	var res LoginResponse
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}

+ 180 - 0
pkg/modelscapi/metrics_request.go

@@ -0,0 +1,180 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package modelscapi
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+	"strconv"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+	"github.com/go-openapi/validate"
+)
+
+// MetricsRequest metrics
+//
+// push metrics model
+//
+// swagger:model MetricsRequest
+type MetricsRequest struct {
+
+	// bouncers
+	// Required: true
+	Bouncers []*MetricsRequestBouncersItem `json:"bouncers"`
+
+	// machines
+	// Required: true
+	Machines []*MetricsRequestMachinesItem `json:"machines"`
+}
+
+// Validate validates this metrics request
+func (m *MetricsRequest) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.validateBouncers(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateMachines(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *MetricsRequest) validateBouncers(formats strfmt.Registry) error {
+
+	if err := validate.Required("bouncers", "body", m.Bouncers); err != nil {
+		return err
+	}
+
+	for i := 0; i < len(m.Bouncers); i++ {
+		if swag.IsZero(m.Bouncers[i]) { // not required
+			continue
+		}
+
+		if m.Bouncers[i] != nil {
+			if err := m.Bouncers[i].Validate(formats); err != nil {
+				if ve, ok := err.(*errors.Validation); ok {
+					return ve.ValidateName("bouncers" + "." + strconv.Itoa(i))
+				} else if ce, ok := err.(*errors.CompositeError); ok {
+					return ce.ValidateName("bouncers" + "." + strconv.Itoa(i))
+				}
+				return err
+			}
+		}
+
+	}
+
+	return nil
+}
+
+func (m *MetricsRequest) validateMachines(formats strfmt.Registry) error {
+
+	if err := validate.Required("machines", "body", m.Machines); err != nil {
+		return err
+	}
+
+	for i := 0; i < len(m.Machines); i++ {
+		if swag.IsZero(m.Machines[i]) { // not required
+			continue
+		}
+
+		if m.Machines[i] != nil {
+			if err := m.Machines[i].Validate(formats); err != nil {
+				if ve, ok := err.(*errors.Validation); ok {
+					return ve.ValidateName("machines" + "." + strconv.Itoa(i))
+				} else if ce, ok := err.(*errors.CompositeError); ok {
+					return ce.ValidateName("machines" + "." + strconv.Itoa(i))
+				}
+				return err
+			}
+		}
+
+	}
+
+	return nil
+}
+
+// ContextValidate validate this metrics request based on the context it is used
+func (m *MetricsRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.contextValidateBouncers(ctx, formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.contextValidateMachines(ctx, formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *MetricsRequest) contextValidateBouncers(ctx context.Context, formats strfmt.Registry) error {
+
+	for i := 0; i < len(m.Bouncers); i++ {
+
+		if m.Bouncers[i] != nil {
+			if err := m.Bouncers[i].ContextValidate(ctx, formats); err != nil {
+				if ve, ok := err.(*errors.Validation); ok {
+					return ve.ValidateName("bouncers" + "." + strconv.Itoa(i))
+				} else if ce, ok := err.(*errors.CompositeError); ok {
+					return ce.ValidateName("bouncers" + "." + strconv.Itoa(i))
+				}
+				return err
+			}
+		}
+
+	}
+
+	return nil
+}
+
+func (m *MetricsRequest) contextValidateMachines(ctx context.Context, formats strfmt.Registry) error {
+
+	for i := 0; i < len(m.Machines); i++ {
+
+		if m.Machines[i] != nil {
+			if err := m.Machines[i].ContextValidate(ctx, formats); err != nil {
+				if ve, ok := err.(*errors.Validation); ok {
+					return ve.ValidateName("machines" + "." + strconv.Itoa(i))
+				} else if ce, ok := err.(*errors.CompositeError); ok {
+					return ce.ValidateName("machines" + "." + strconv.Itoa(i))
+				}
+				return err
+			}
+		}
+
+	}
+
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *MetricsRequest) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *MetricsRequest) UnmarshalBinary(b []byte) error {
+	var res MetricsRequest
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}

+ 59 - 0
pkg/modelscapi/metrics_request_bouncers_item.go

@@ -0,0 +1,59 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package modelscapi
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+)
+
+// MetricsRequestBouncersItem MetricsBouncerInfo
+//
+// swagger:model MetricsRequestBouncersItem
+type MetricsRequestBouncersItem struct {
+
+	// bouncer name
+	CustomName string `json:"custom_name,omitempty"`
+
+	// last bouncer pull date
+	LastPull string `json:"last_pull,omitempty"`
+
+	// bouncer type (firewall, php...)
+	Name string `json:"name,omitempty"`
+
+	// bouncer version
+	Version string `json:"version,omitempty"`
+}
+
+// Validate validates this metrics request bouncers item
+func (m *MetricsRequestBouncersItem) Validate(formats strfmt.Registry) error {
+	return nil
+}
+
+// ContextValidate validates this metrics request bouncers item based on context it is used
+func (m *MetricsRequestBouncersItem) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *MetricsRequestBouncersItem) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *MetricsRequestBouncersItem) UnmarshalBinary(b []byte) error {
+	var res MetricsRequestBouncersItem
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}

+ 59 - 0
pkg/modelscapi/metrics_request_machines_item.go

@@ -0,0 +1,59 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package modelscapi
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+)
+
+// MetricsRequestMachinesItem MetricsAgentInfo
+//
+// swagger:model MetricsRequestMachinesItem
+type MetricsRequestMachinesItem struct {
+
+	// last agent push date
+	LastPush string `json:"last_push,omitempty"`
+
+	// last agent update date
+	LastUpdate string `json:"last_update,omitempty"`
+
+	// agent name
+	Name string `json:"name,omitempty"`
+
+	// agent version
+	Version string `json:"version,omitempty"`
+}
+
+// Validate validates this metrics request machines item
+func (m *MetricsRequestMachinesItem) Validate(formats strfmt.Registry) error {
+	return nil
+}
+
+// ContextValidate validates this metrics request machines item based on context it is used
+func (m *MetricsRequestMachinesItem) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *MetricsRequestMachinesItem) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *MetricsRequestMachinesItem) UnmarshalBinary(b []byte) error {
+	var res MetricsRequestMachinesItem
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}

+ 95 - 0
pkg/modelscapi/register_request.go

@@ -0,0 +1,95 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package modelscapi
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+	"github.com/go-openapi/validate"
+)
+
+// RegisterRequest register request
+//
+// # Register request model
+//
+// swagger:model RegisterRequest
+type RegisterRequest struct {
+
+	// machine_id is a (username) generated by crowdsec
+	// Required: true
+	// Pattern: ^[a-zA-Z0-9]+$
+	MachineID *string `json:"machine_id"`
+
+	// Password, should respect the password policy (link to add)
+	// Required: true
+	Password *string `json:"password"`
+}
+
+// Validate validates this register request
+func (m *RegisterRequest) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.validateMachineID(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validatePassword(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *RegisterRequest) validateMachineID(formats strfmt.Registry) error {
+
+	if err := validate.Required("machine_id", "body", m.MachineID); err != nil {
+		return err
+	}
+
+	if err := validate.Pattern("machine_id", "body", *m.MachineID, `^[a-zA-Z0-9]+$`); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *RegisterRequest) validatePassword(formats strfmt.Registry) error {
+
+	if err := validate.Required("password", "body", m.Password); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ContextValidate validates this register request based on context it is used
+func (m *RegisterRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *RegisterRequest) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *RegisterRequest) UnmarshalBinary(b []byte) error {
+	var res RegisterRequest
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}

+ 105 - 0
pkg/modelscapi/reset_password_request.go

@@ -0,0 +1,105 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package modelscapi
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+	"github.com/go-openapi/validate"
+)
+
+// ResetPasswordRequest resetPassword
+//
+// # ResetPassword request model
+//
+// swagger:model ResetPasswordRequest
+type ResetPasswordRequest struct {
+
+	// machine_id is a (username) generated by crowdsec
+	// Required: true
+	// Max Length: 48
+	// Min Length: 48
+	// Pattern: ^[a-zA-Z0-9]+$
+	MachineID *string `json:"machine_id"`
+
+	// Password, should respect the password policy (link to add)
+	// Required: true
+	Password *string `json:"password"`
+}
+
+// Validate validates this reset password request
+func (m *ResetPasswordRequest) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.validateMachineID(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validatePassword(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *ResetPasswordRequest) validateMachineID(formats strfmt.Registry) error {
+
+	if err := validate.Required("machine_id", "body", m.MachineID); err != nil {
+		return err
+	}
+
+	if err := validate.MinLength("machine_id", "body", *m.MachineID, 48); err != nil {
+		return err
+	}
+
+	if err := validate.MaxLength("machine_id", "body", *m.MachineID, 48); err != nil {
+		return err
+	}
+
+	if err := validate.Pattern("machine_id", "body", *m.MachineID, `^[a-zA-Z0-9]+$`); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *ResetPasswordRequest) validatePassword(formats strfmt.Registry) error {
+
+	if err := validate.Required("password", "body", m.Password); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ContextValidate validates this reset password request based on context it is used
+func (m *ResetPasswordRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *ResetPasswordRequest) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *ResetPasswordRequest) UnmarshalBinary(b []byte) error {
+	var res ResetPasswordRequest
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}

+ 73 - 0
pkg/modelscapi/success_response.go

@@ -0,0 +1,73 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+package modelscapi
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+	"github.com/go-openapi/validate"
+)
+
+// SuccessResponse success response
+//
+// success response return by the API
+//
+// swagger:model SuccessResponse
+type SuccessResponse struct {
+
+	// message
+	// Required: true
+	Message *string `json:"message"`
+}
+
+// Validate validates this success response
+func (m *SuccessResponse) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.validateMessage(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *SuccessResponse) validateMessage(formats strfmt.Registry) error {
+
+	if err := validate.Required("message", "body", m.Message); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ContextValidate validates this success response based on context it is used
+func (m *SuccessResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *SuccessResponse) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *SuccessResponse) UnmarshalBinary(b []byte) error {
+	var res SuccessResponse
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}

+ 2 - 0
pkg/types/constants.go

@@ -14,6 +14,8 @@ const CscliImportOrigin = "cscli-import"
 const ListOrigin = "lists"
 const CAPIOrigin = "CAPI"
 
+const DecisionTypeBan = "ban"
+
 func GetOrigins() []string {
 	return []string{
 		CscliOrigin,