{"record":{"id":"b61a8d5dcdab77a8","repo":"netbirdio/netbird","slug":"either-peer-or-peer-groups-must-be-provided","errorCode":null,"errorMessage":"either peer or peer_groups must be provided","messagePattern":"either peer or peer_groups must be provided","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"management/server/networks/routers/types/router.go","lineNumber":80,"sourceCode":"\t\tMasquerade: masquerade,\n\t\tMetric:     metric,\n\t\tEnabled:    enabled,\n\t}\n\n\tif err := r.Validate(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn r, nil\n}\n\nfunc (n *NetworkRouter) Validate() error {\n\tif n.Peer != \"\" && len(n.PeerGroups) > 0 {\n\t\treturn errors.New(\"peer and peer_groups cannot be set at the same time\")\n\t}\n\n\tif n.Peer == \"\" && len(n.PeerGroups) == 0 {\n\t\treturn errors.New(\"either peer or peer_groups must be provided\")\n\t}\n\n\treturn nil\n}\n\nfunc (n *NetworkRouter) ToAPIResponse() *api.NetworkRouter {\n\treturn &api.NetworkRouter{\n\t\tId:         n.ID,\n\t\tPeer:       &n.Peer,\n\t\tPeerGroups: &n.PeerGroups,\n\t\tMasquerade: n.Masquerade,\n\t\tMetric:     n.Metric,\n\t\tEnabled:    n.Enabled,\n\t}\n}\n\nfunc (n *NetworkRouter) FromAPIRequest(req *api.NetworkRouterRequest) {\n\tif req.Peer != nil {","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/management/server/networks/routers/types/router.go#L62-L98","documentation":"Returned by NetworkRouter.Validate (management/server/networks/routers/types/router.go:80): a network router has no destination peer at all. The peer-routing model requires the traffic to be terminated by either one peer or the members of at least one access group, so a router with neither is rejected before persistence.","triggerScenarios":"POST /api/networks/{networkId}/routers (or update) where peer is empty/omitted and peer_groups is empty or omitted.","commonSituations":"Creating a router shell first and intending to attach a peer later (not supported); JSON marshalling that drops empty fields; frontend sending an empty array for peer_groups and empty string for peer.","solutions":["Add a routing peer ID in the peer field, or at least one valid group ID in peer_groups","Create the peer or the group first if it does not exist yet, then create the router"],"exampleFix":"// before\n{ \"network_id\": \"nwr-1\", \"enabled\": true }  // no peer, no peer_groups\n\n// after\n{ \"network_id\": \"nwr-1\", \"peer\": \"p-1\", \"enabled\": true }","handlingStrategy":"validation","validationCode":"func hasRoutingPeer(peer string, groups []string) bool {\n    return peer != \"\" || len(groups) > 0\n}\n\nif !hasRoutingPeer(payload.Peer, payload.PeerGroups) {\n    return errors.New(\"provide a routing peer or at least one peer group\")\n}","typeGuard":null,"tryCatchPattern":"if err := payload.Validate(); err != nil { // client-side mirror of server Validate()\n    return err\n}\n_, err := createRouter(ctx, networkID, payload)\nif err != nil && strings.Contains(err.Error(), \"either peer or peer_groups\") {\n    // prompt for a peer/group selection instead of retrying blindly\n}","preventionTips":["Make peer/peer_groups required input in forms and scripts; do not support 'create now, attach later'","Create the routing peer or group first, then the router, in provisioning pipelines","Assert exactly one of the two fields is non-empty in a shared client-side validator"],"tags":["management","api","networks","routers","validation"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}