Repository: gravitee-io/graviteeio-access-management
Pull request #762 (2020-04-10 15:35:59)

feat(entrypoints): add gateway entrypoints to an organization
Closes gravitee-io/issues#3438
Closes gravitee-io/issues#3437
Closes gravitee-io/issues#3456
Closes gravitee-io/issues#3517

jhaeyaert: Do not forget to drop your database before testing (default entrypoint is created when default organization is created).

Patch:
diff --git a/gravitee-am-common/src/main/java/io/gravitee/am/common/audit/EntityType.java b/gravitee-am-common/src/main/java/io/gravitee/am/common/audit/EntityType.java
index ec566e969a..5a1d72f2cc 100644
--- a/gravitee-am-common/src/main/java/io/gravitee/am/common/audit/EntityType.java
+++ b/gravitee-am-common/src/main/java/io/gravitee/am/common/audit/EntityType.java
@@ -41,4 +41,5 @@ public interface EntityType {
     String FACTOR = "FACTOR";
     String ORGANIZATION = "ORGANIZATION";
     String ENVIRONMENT = "ENVIRONMENT";
+    String ENTRYPOINT = "ENTRYPOINT";
 }
diff --git a/gravitee-am-common/src/main/java/io/gravitee/am/common/audit/EventType.java b/gravitee-am-common/src/main/java/io/gravitee/am/common/audit/EventType.java
index bef7f6f4ce..12e9cf015c 100644
--- a/gravitee-am-common/src/main/java/io/gravitee/am/common/audit/EventType.java
+++ b/gravitee-am-common/src/main/java/io/gravitee/am/common/audit/EventType.java
@@ -213,6 +213,15 @@ public interface EventType {
     String ENVIRONMENT_CREATED = "ENVIRONMENT_CREATED";
     String ENVIRONMENT_UPDATED = "ENVIRONMENT_UPDATED";
 
+    /**
+     * ----------
+     * Entrypoint audit log actions
+     * ----------
+     */
+    String ENTRYPOINT_CREATED = "ENTRYPOINT_CREATED";
+    String ENTRYPOINT_UPDATED = "ENTRYPOINT_UPDATED";
+    String ENTRYPOINT_DELETED = "ENTRYPOINT_DELETED";
+
 
     static Collection<String> types() {
         return new TreeSet(Arrays.asList(
@@ -235,7 +244,8 @@ public interface EventType {
                 MEMBERSHIP_CREATED, MEMBERSHIP_UPDATED, MEMBERSHIP_DELETED,
                 FACTOR_CREATED, FACTOR_UPDATED, FACTOR_DELETED,
                 ORGANIZATION_CREATED, ORGANIZATION_UPDATED,
-                ENVIRONMENT_CREATED, ENVIRONMENT_UPDATED
+                ENVIRONMENT_CREATED, ENVIRONMENT_UPDATED,
+                ENTRYPOINT_CREATED, ENTRYPOINT_UPDATED, ENTRYPOINT_DELETED
         ));
     }
 }
\ No newline at end of file
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/OrganizationResource.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/OrganizationResource.java
index 67ccd965e8..6aef03ab00 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/OrganizationResource.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/OrganizationResource.java
@@ -18,6 +18,7 @@ package io.gravitee.am.management.handlers.management.api.resources.organization
 import io.gravitee.am.identityprovider.api.User;
 import io.gravitee.am.management.handlers.management.api.resources.AbstractResource;
 import io.gravitee.am.management.handlers.management.api.resources.organizations.audits.AuditsResource;
+import io.gravitee.am.management.handlers.management.api.resources.organizations.entrypoints.EntrypointsResource;
 import io.gravitee.am.management.handlers.management.api.resources.organizations.environments.EnvironmentsResource;
 import io.gravitee.am.management.handlers.management.api.resources.organizations.forms.FormsResource;
 import io.gravitee.am.management.handlers.management.api.resources.organizations.groups.GroupsResource;
@@ -106,6 +107,11 @@ public class OrganizationResource extends AbstractResource {
         return resourceContext.getResource(TagsResource.class);
     }
 
+    @Path("entrypoints")
+    public EntrypointsResource getEntrypointsResource() {
+        return resourceContext.getResource(EntrypointsResource.class);
+    }
+
     @Path("roles")
     public RolesResource getRolesResource() {
         return resourceContext.getResource(RolesResource.class);
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/entrypoints/EntrypointResource.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/entrypoints/EntrypointResource.java
new file mode 100644
index 0000000000..2d232dc7ec
--- /dev/null
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/entrypoints/EntrypointResource.java
@@ -0,0 +1,108 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.gravitee.am.management.handlers.management.api.resources.organizations.entrypoints;
+
+import io.gravitee.am.identityprovider.api.User;
+import io.gravitee.am.management.handlers.management.api.resources.AbstractResource;
+import io.gravitee.am.model.Acl;
+import io.gravitee.am.model.ReferenceType;
+import io.gravitee.am.model.Entrypoint;
+import io.gravitee.am.model.permissions.Permission;
+import io.gravitee.am.service.EntrypointService;
+import io.gravitee.am.service.exception.EntrypointNotFoundException;
+import io.gravitee.am.service.model.UpdateEntrypoint;
+import io.gravitee.common.http.MediaType;
+import io.reactivex.Maybe;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import javax.ws.rs.*;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.ResourceContext;
+import javax.ws.rs.container.Suspended;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class EntrypointResource extends AbstractResource {
+
+    @Autowired
+    private EntrypointService entrypointService;
+
+    @Context
+    private ResourceContext resourceContext;
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @ApiOperation(value = "Get a sharding entrypoint",
+            notes = "User must have the ORGANIZATION_ENTRYPOINT[READ] permission on the specified organization")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "Sharding entrypoint", response = Entrypoint.class),
+            @ApiResponse(code = 500, message = "Internal server error")})
+    public void get(
+            @PathParam("organizationId") String organizationId,
+            @PathParam("entrypointId") String entrypointId, @Suspended final AsyncResponse response) {
+
+        checkPermission(ReferenceType.ORGANIZATION, organizationId, Permission.ORGANIZATION_ENTRYPOINT, Acl.READ)
+                .andThen(entrypointService.findById(entrypointId, organizationId))
+                .subscribe(response::resume, response::resume);
+    }
+
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @ApiOperation(value = "Update the sharding entrypoint",
+            notes = "User must have the ORGANIZATION_ENTRYPOINT[UPDATE] permission on the specified organization")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "Sharding entrypoint successfully updated", response = Entrypoint.class),
+            @ApiResponse(code = 500, message = "Internal server error")})
+    public void update(
+            @PathParam("organizationId") String organizationId,
+            @ApiParam(name = "entrypoint", required = true) @Valid @NotNull final UpdateEntrypoint entrypointToUpdate,
+            @PathParam("entrypointId") String entrypointId,
+            @Suspended final AsyncResponse response) {
+        final User authenticatedUser = getAuthenticatedUser();
+
+        checkPermission(ReferenceType.ORGANIZATION, organizationId, Permission.ORGANIZATION_ENTRYPOINT, Acl.UPDATE)
+                .andThen(entrypointService.update(entrypointId, organizationId, entrypointToUpdate, authenticatedUser))
+                .subscribe(response::resume, response::resume);
+    }
+
+    @DELETE
+    @ApiOperation(value = "Delete the sharding entrypoint",
+            notes = "User must have the ORGANIZATION_ENTRYPOINT[DELETE] permission on the specified organization")
+    @ApiResponses({
+            @ApiResponse(code = 204, message = "Sharding entrypoint successfully deleted"),
+            @ApiResponse(code = 500, message = "Internal server error")})
+    public void delete(
+            @PathParam("organizationId") String organizationId,
+            @PathParam("entrypointId") String entrypointId,
+            @Suspended final AsyncResponse response) {
+        final User authenticatedUser = getAuthenticatedUser();
+
+        checkPermission(ReferenceType.ORGANIZATION, organizationId, Permission.ORGANIZATION_ENTRYPOINT, Acl.DELETE)
+                .andThen(entrypointService.delete(entrypointId, organizationId, authenticatedUser))
+                .subscribe(() -> response.resume(Response.noContent().build()), response::resume);
+    }
+}
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/entrypoints/EntrypointsResource.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/entrypoints/EntrypointsResource.java
new file mode 100644
index 0000000000..afb1c8380c
--- /dev/null
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/entrypoints/EntrypointsResource.java
@@ -0,0 +1,114 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.gravitee.am.management.handlers.management.api.resources.organizations.entrypoints;
+
+import io.gravitee.am.identityprovider.api.User;
+import io.gravitee.am.management.handlers.management.api.resources.AbstractResource;
+import io.gravitee.am.model.Acl;
+import io.gravitee.am.model.Domain;
+import io.gravitee.am.model.Entrypoint;
+import io.gravitee.am.model.ReferenceType;
+import io.gravitee.am.model.permissions.Permission;
+import io.gravitee.am.service.EntrypointService;
+import io.gravitee.am.service.model.NewEntrypoint;
+import io.gravitee.common.http.MediaType;
+import io.swagger.annotations.*;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import javax.ws.rs.*;
+import javax.ws.rs.container.AsyncResponse;
+import javax.ws.rs.container.ResourceContext;
+import javax.ws.rs.container.Suspended;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import java.net.URI;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Api(tags = {"entrypoints"})
+public class EntrypointsResource extends AbstractResource {
+
+    @Autowired
+    private EntrypointService entrypointService;
+
+    @Context
+    private ResourceContext resourceContext;
+
+    @GET
+    @Produces(MediaType.APPLICATION_JSON)
+    @ApiOperation(
+            value = "List entrypoints",
+            notes = "User must have the ORGANIZATION[LIST] permission on the specified organization. " +
+                    "Each returned entrypoint is filtered and contains only basic information such as id and name.")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "List all the entrypoints", response = Domain.class, responseContainer = "List"),
+            @ApiResponse(code = 500, message = "Internal server error")})
+    public void list(
+            @PathParam("organizationId") String organizationId,
+            @Suspended final AsyncResponse response) {
+
+        checkPermission(ReferenceType.ORGANIZATION, organizationId, Permission.ORGANIZATION_ENTRYPOINT, Acl.LIST)
+                .andThen(entrypointService.findAll(organizationId))
+                .map(this::filterEntrypointInfos)
+                .sorted((o1, o2) -> String.CASE_INSENSITIVE_ORDER.compare(o1.getName(), o2.getName()))
+                .toList()
+                .subscribe(response::resume, response::resume);
+    }
+
+    @POST
+    @Produces(MediaType.APPLICATION_JSON)
+    @Consumes(MediaType.APPLICATION_JSON)
+    @ApiOperation(value = "Create a entrypoint",
+            notes = "User must have the ORGANIZATION_ENTRYPOINT[CREATE] permission on the specified organization")
+    @ApiResponses({
+            @ApiResponse(code = 201, message = "Entrypoint successfully created"),
+            @ApiResponse(code = 500, message = "Internal server error")})
+    public void create(
+            @PathParam("organizationId") String organizationId,
+            @ApiParam(name = "entrypoint", required = true)
+            @Valid @NotNull final NewEntrypoint newEntrypoint,
+            @Suspended final AsyncResponse response) {
+        final User authenticatedUser = getAuthenticatedUser();
+
+        checkPermission(ReferenceType.ORGANIZATION, organizationId, Permission.ORGANIZATION_ENTRYPOINT, Acl.CREATE)
+                .andThen(entrypointService.create(organizationId, newEntrypoint, authenticatedUser))
+                .subscribe(entrypoint -> response.resume(Response
+                                .created(URI.create("/organizations/" + organizationId + "/entrypoints/" + entrypoint.getId()))
+                                .entity(entrypoint)
+                                .build()),
+                        response::resume);
+    }
+
+    @Path("{entrypointId}")
+    public EntrypointResource getEntrypointResource() {
+        return resourceContext.getResource(EntrypointResource.class);
+    }
+
+    private Entrypoint filterEntrypointInfos(Entrypoint entrypoint) {
+        Entrypoint filteredEntrypoint = new Entrypoint();
+        filteredEntrypoint.setId(entrypoint.getId());
+        filteredEntrypoint.setName(entrypoint.getName());
+        filteredEntrypoint.setUrl(entrypoint.getUrl());
+        filteredEntrypoint.setDescription(entrypoint.getDescription());
+        filteredEntrypoint.setDefaultEntrypoint(entrypoint.isDefaultEntrypoint());
+
+        return filteredEntrypoint;
+    }
+}
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/DomainResource.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/DomainResource.java
index 831dba3468..280a537c96 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/DomainResource.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/DomainResource.java
@@ -20,14 +20,20 @@ import io.gravitee.am.management.handlers.management.api.resources.AbstractResou
 import io.gravitee.am.management.service.AuditReporterManager;
 import io.gravitee.am.model.Acl;
 import io.gravitee.am.model.Domain;
+import io.gravitee.am.model.Entrypoint;
 import io.gravitee.am.model.ReferenceType;
 import io.gravitee.am.model.permissions.Permission;
 import io.gravitee.am.service.DomainService;
+import io.gravitee.am.service.EntrypointService;
 import io.gravitee.am.service.exception.DomainNotFoundException;
+import io.gravitee.am.service.exception.EntrypointNotFoundException;
 import io.gravitee.am.service.model.PatchDomain;
 import io.gravitee.common.http.MediaType;
 import io.reactivex.Completable;
 import io.reactivex.Maybe;
+import io.reactivex.Single;
+import io.reactivex.functions.Function;
+import io.reactivex.functions.Predicate;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
 import io.swagger.annotations.ApiResponse;
@@ -42,9 +48,12 @@ import javax.ws.rs.container.ResourceContext;
 import javax.ws.rs.container.Suspended;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.Response;
+import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * @author David BRASSELY (david.brassely at graviteesource.com)
@@ -62,6 +71,9 @@ public class DomainResource extends AbstractResource {
     @Autowired
     private AuditReporterManager auditReporterManager;
 
+    @Autowired
+    private EntrypointService entrypointService;
+
     @GET
     @Produces(MediaType.APPLICATION_JSON)
     @ApiOperation(value = "Get a security domain",
@@ -148,6 +160,31 @@ public class DomainResource extends AbstractResource {
                 .subscribe(() -> response.resume(Response.noContent().build()), response::resume);
     }
 
+    @GET
+    @Path("/entrypoints")
+    @Produces(MediaType.APPLICATION_JSON)
+    @ApiOperation(value = "Get the matching gateway entrypoint of the domain",
+            notes = "User must have the DOMAIN[READ] permission on the specified domain, environment or organization. " +
+                    "Domain will be filtered according to permissions (READ on DOMAIN_USER_ACCOUNT, DOMAIN_IDENTITY_PROVIDER, DOMAIN_FORM, DOMAIN_LOGIN_SETTINGS, " +
+                    "DOMAIN_DCR, DOMAIN_SCIM, DOMAIN_SETTINGS)")
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "Domain entrypoint", response = Entrypoint.class, responseContainer = "List"),
+            @ApiResponse(code = 500, message = "Internal server error")})
+    public void getEntrypoints(
+            @PathParam("organizationId") String organizationId,
+            @PathParam("environmentId") String environmentId,
+            @PathParam("domain") String domainId,
+            @Suspended final AsyncResponse response) {
+
+        checkAnyPermission(organizationId, environmentId, domainId, Permission.DOMAIN, Acl.READ)
+                .andThen(domainService.findById(domainId)
+                        .switchIfEmpty(Maybe.error(new DomainNotFoundException(domainId)))
+                        .flatMapSingle(domain -> entrypointService.findAll(organizationId)
+                                .toList()
+                                .map(entrypoints -> filterEntrypoints(entrypoints, domain))))
+                .subscribe(response::resume, response::resume);
+    }
+
     @Path("clients")
     public ClientsResource getClientsResource() {
         return resourceContext.getResource(ClientsResource.class);
@@ -284,4 +321,25 @@ public class DomainResource extends AbstractResource {
 
         return filteredDomain;
     }
+
+    /**
+     * Filter a list of entrypoints depending on domain tags.
+     * Given a domain with tags [ A, B ], then entrypoint must has either A or B tag defined.
+     * If no entrypoint has been retained, the default entrypoint is returned.
+     *
+     * @param domain the domain.
+     * @return a filtered list of entrypoints.
+     */
+    private List<Entrypoint> filterEntrypoints(List<Entrypoint> entrypoints, Domain domain) {
+
+        List<Entrypoint> filteredEntrypoints = entrypoints.stream().filter(entrypoint -> entrypoint.isDefaultEntrypoint()
+                || (entrypoint.getTags() != null && !entrypoint.getTags().isEmpty() && domain.getTags() != null && entrypoint.getTags().stream().anyMatch(tag -> domain.getTags().contains(tag)))).collect(Collectors.toList());
+
+        if (filteredEntrypoints.size() > 1) {
+            // Remove default entrypoint if another entrypoint has matched.
+            filteredEntrypoints.removeIf(Entrypoint::isDefaultEntrypoint);
+        }
+
+        return filteredEntrypoints;
+    }
 }
diff --git a/gravitee-am-model/src/main/java/io/gravitee/am/model/Entrypoint.java b/gravitee-am-model/src/main/java/io/gravitee/am/model/Entrypoint.java
new file mode 100644
index 0000000000..68ac060a59
--- /dev/null
+++ b/gravitee-am-model/src/main/java/io/gravitee/am/model/Entrypoint.java
@@ -0,0 +1,147 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.gravitee.am.model;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class Entrypoint {
+
+    private String id;
+    private String name;
+    private String description;
+    private String url;
+    private List<String> tags;
+    private String organizationId;
+    private boolean defaultEntrypoint;
+    private Date createdAt;
+    private Date updatedAt;
+
+    public Entrypoint(){}
+
+    public Entrypoint(Entrypoint other) {
+        this.id = other.id;
+        this.name = other.name;
+        this.description = other.description;
+        this.url = other.url;
+        this.tags = other.tags;
+        this.organizationId = other.organizationId;
+        this.defaultEntrypoint = other.defaultEntrypoint;
+        this.createdAt = other.createdAt;
+        this.updatedAt = other.updatedAt;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    public List<String> getTags() {
+        return tags;
+    }
+
+    public void setTags(List<String> tags) {
+        this.tags = tags;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof Entrypoint)) return false;
+        Entrypoint that = (Entrypoint) o;
+        return Objects.equals(id, that.id) &&
+                Objects.equals(url, that.url) &&
+                Objects.equals(tags, that.tags);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id, url, tags);
+    }
+
+    @Override
+    public String toString() {
+        return "EntrypointEntity{" +
+                "id='" + id + '\'' +
+                ", value='" + url + '\'' +
+                ", tags=" + tags +
+                '}';
+    }
+
+    public String getOrganizationId() {
+        return organizationId;
+    }
+
+    public void setOrganizationId(String organizationId) {
+        this.organizationId = organizationId;
+    }
+
+    public Date getCreatedAt() {
+        return createdAt;
+    }
+
+    public void setCreatedAt(Date createdAt) {
+        this.createdAt = createdAt;
+    }
+
+    public Date getUpdatedAt() {
+        return updatedAt;
+    }
+
+    public void setUpdatedAt(Date updatedAt) {
+        this.updatedAt = updatedAt;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public boolean isDefaultEntrypoint() {
+        return defaultEntrypoint;
+    }
+
+    public void setDefaultEntrypoint(boolean defaultEntrypoint) {
+        this.defaultEntrypoint = defaultEntrypoint;
+    }
+}
diff --git a/gravitee-am-model/src/main/java/io/gravitee/am/model/permissions/Permission.java b/gravitee-am-model/src/main/java/io/gravitee/am/model/permissions/Permission.java
index 358c62a2d0..fcb4b3c2bb 100644
--- a/gravitee-am-model/src/main/java/io/gravitee/am/model/permissions/Permission.java
+++ b/gravitee-am-model/src/main/java/io/gravitee/am/model/permissions/Permission.java
@@ -38,6 +38,7 @@ public enum Permission {
     ORGANIZATION_GROUP(ReferenceType.ORGANIZATION),
     ORGANIZATION_ROLE(ReferenceType.ORGANIZATION),
     ORGANIZATION_TAG(ReferenceType.ORGANIZATION),
+    ORGANIZATION_ENTRYPOINT(ReferenceType.ORGANIZATION),
     ORGANIZATION_FORM(ReferenceType.ORGANIZATION),
     ORGANIZATION_MEMBER(ReferenceType.ORGANIZATION),
 
diff --git a/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/EntrypointRepository.java b/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/EntrypointRepository.java
new file mode 100644
index 0000000000..0a5a87993b
--- /dev/null
+++ b/gravitee-am-repository/gravitee-am-repository-api/src/main/java/io/gravitee/am/repository/management/api/EntrypointRepository.java
@@ -0,0 +1,33 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.gravitee.am.repository.management.api;
+
+import io.gravitee.am.model.Entrypoint;
+import io.gravitee.am.model.Tag;
+import io.gravitee.am.repository.common.CrudRepository;
+import io.reactivex.Flowable;
+import io.reactivex.Maybe;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public interface EntrypointRepository extends CrudRepository<Entrypoint, String> {
+
+    Maybe<Entrypoint> findById(String id, String organizationId);
+
+    Flowable<Entrypoint> findAll(String organizationId);
+}
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoEntrypointRepository.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoEntrypointRepository.java
new file mode 100644
index 0000000000..5b74b1c8c8
--- /dev/null
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/MongoEntrypointRepository.java
@@ -0,0 +1,117 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.gravitee.am.repository.mongodb.management;
+
+import com.mongodb.reactivestreams.client.MongoCollection;
+import io.gravitee.am.common.utils.RandomString;
+import io.gravitee.am.model.Entrypoint;
+import io.gravitee.am.repository.management.api.EntrypointRepository;
+import io.gravitee.am.repository.mongodb.management.internal.model.EntrypointMongo;
+import io.reactivex.*;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+
+import static com.mongodb.client.model.Filters.and;
+import static com.mongodb.client.model.Filters.eq;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Component
+public class MongoEntrypointRepository extends AbstractManagementMongoRepository implements EntrypointRepository {
+
+    private static final String FIELD_ID = "_id";
+    private static final String FIELD_ORGANIZATION_ID = "organizationId";
+    private MongoCollection<EntrypointMongo> collection;
+
+    @PostConstruct
+    public void init() {
+        collection = mongoOperations.getCollection("entrypoints", EntrypointMongo.class);
+    }
+
+    @Override
+    public Maybe<Entrypoint> findById(String id, String organizationId) {
+        return Observable.fromPublisher(collection.find(and(eq(FIELD_ORGANIZATION_ID, organizationId), eq(FIELD_ID, id))).first()).firstElement().map(this::convert);
+    }
+
+    @Override
+    public Maybe<Entrypoint> findById(String id) {
+        return Observable.fromPublisher(collection.find(eq(FIELD_ID, id)).first()).firstElement().map(this::convert);
+    }
+
+    @Override
+    public Flowable<Entrypoint> findAll(String organizationId) {
+        return Flowable.fromPublisher(collection.find(eq("organizationId", organizationId))).map(this::convert);
+    }
+
+    @Override
+    public Single<Entrypoint> create(Entrypoint item) {
+        EntrypointMongo entrypoint = convert(item);
+        entrypoint.setId(entrypoint.getId() == null ? RandomString.generate() : entrypoint.getId());
+        return Single.fromPublisher(collection.insertOne(entrypoint)).flatMap(success -> findById(entrypoint.getId()).toSingle());
+    }
+
+    @Override
+    public Single<Entrypoint> update(Entrypoint item) {
+        EntrypointMongo entrypoint = convert(item);
+        return Single.fromPublisher(collection.replaceOne(eq(FIELD_ID, entrypoint.getId()), entrypoint)).flatMap(updateResult -> findById(entrypoint.getId()).toSingle());
+    }
+
+    @Override
+    public Completable delete(String id) {
+        return Completable.fromPublisher(collection.deleteOne(eq(FIELD_ID, id)));
+    }
+
+    private Entrypoint convert(EntrypointMongo entrypointMongo) {
+        if (entrypointMongo == null) {
+            return null;
+        }
+
+        Entrypoint entrypoint = new Entrypoint();
+        entrypoint.setId(entrypointMongo.getId());
+        entrypoint.setName(entrypointMongo.getName());
+        entrypoint.setDescription(entrypointMongo.getDescription());
+        entrypoint.setUrl(entrypointMongo.getUrl());
+        entrypoint.setTags(entrypointMongo.getTags());
+        entrypoint.setOrganizationId(entrypointMongo.getOrganizationId());
+        entrypoint.setDefaultEntrypoint(entrypointMongo.isDefaultEntrypoint());
+        entrypoint.setCreatedAt(entrypointMongo.getCreatedAt());
+        entrypoint.setUpdatedAt(entrypointMongo.getUpdatedAt());
+
+        return entrypoint;
+    }
+
+    private EntrypointMongo convert(Entrypoint entrypoint) {
+        if (entrypoint == null) {
+            return null;
+        }
+
+        EntrypointMongo entrypointMongo = new EntrypointMongo();
+        entrypointMongo.setId(entrypoint.getId());
+        entrypointMongo.setName(entrypoint.getName());
+        entrypointMongo.setDescription(entrypoint.getDescription());
+        entrypointMongo.setUrl(entrypoint.getUrl());
+        entrypointMongo.setTags(entrypoint.getTags());
+        entrypointMongo.setOrganizationId(entrypoint.getOrganizationId());
+        entrypointMongo.setDefaultEntrypoint(entrypoint.isDefaultEntrypoint());
+        entrypointMongo.setCreatedAt(entrypoint.getCreatedAt());
+        entrypointMongo.setUpdatedAt(entrypoint.getUpdatedAt());
+
+        return entrypointMongo;
+    }
+}
\ No newline at end of file
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/EntrypointMongo.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/EntrypointMongo.java
new file mode 100644
index 0000000000..cd0cdf8290
--- /dev/null
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/main/java/io/gravitee/am/repository/mongodb/management/internal/model/EntrypointMongo.java
@@ -0,0 +1,91 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.gravitee.am.repository.mongodb.management.internal.model;
+
+import io.gravitee.am.repository.mongodb.common.model.Auditable;
+
+import java.util.List;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class EntrypointMongo  extends Auditable {
+
+    private String id;
+    private String name;
+    private String description;
+    private String url;
+    private List<String> tags;
+    private String organizationId;
+    private boolean defaultEntrypoint;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    public List<String> getTags() {
+        return tags;
+    }
+
+    public void setTags(List<String> tags) {
+        this.tags = tags;
+    }
+
+    public String getOrganizationId() {
+        return organizationId;
+    }
+
+    public void setOrganizationId(String organizationId) {
+        this.organizationId = organizationId;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public boolean isDefaultEntrypoint() {
+        return defaultEntrypoint;
+    }
+
+    public void setDefaultEntrypoint(boolean defaultEntrypoint) {
+        this.defaultEntrypoint = defaultEntrypoint;
+    }
+}
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/EntrypointService.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/EntrypointService.java
new file mode 100644
index 0000000000..e28bb4c2e3
--- /dev/null
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/EntrypointService.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.gravitee.am.service;
+
+import io.gravitee.am.identityprovider.api.User;
+import io.gravitee.am.model.Entrypoint;
+import io.gravitee.am.service.model.NewEntrypoint;
+import io.gravitee.am.service.model.UpdateEntrypoint;
+import io.reactivex.Completable;
+import io.reactivex.Flowable;
+import io.reactivex.Single;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public interface EntrypointService {
+
+    Single<Entrypoint> findById(String id, String organizationId);
+
+    Flowable<Entrypoint> findAll(String organizationId);
+
+    Single<Entrypoint> create(String organizationId, NewEntrypoint entrypoint, User principal);
+
+    Single<Entrypoint> createDefault(String organizationId);
+
+    Single<Entrypoint> update(String entrypointId, String organizationId, UpdateEntrypoint entrypoint, User principal);
+
+    Completable delete(String entrypointId, String organizationId, User principal);
+}
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/exception/EntrypointNotFoundException.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/exception/EntrypointNotFoundException.java
new file mode 100644
index 0000000000..93363a89c7
--- /dev/null
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/exception/EntrypointNotFoundException.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.gravitee.am.service.exception;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class EntrypointNotFoundException extends AbstractNotFoundException {
+
+    private final String entrypoint;
+
+    public EntrypointNotFoundException(String entrypoint) {
+        this.entrypoint = entrypoint;
+    }
+
+    @Override
+    public String getMessage() {
+        return "Entrypoint [" + entrypoint + "] can not be found.";
+    }
+}
\ No newline at end of file
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/exception/InvalidEntrypointException.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/exception/InvalidEntrypointException.java
new file mode 100644
index 0000000000..0f1eba0c0a
--- /dev/null
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/exception/InvalidEntrypointException.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.gravitee.am.service.exception;
+
+import io.gravitee.common.http.HttpStatusCode;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class InvalidEntrypointException extends AbstractManagementException {
+
+    private final String msg;
+
+    public InvalidEntrypointException(String msg) {
+        this.msg = msg;
+    }
+
+    @Override
+    public int getHttpStatusCode() {
+        return HttpStatusCode.BAD_REQUEST_400;
+    }
+
+    @Override
+    public String getMessage() {
+        return msg;
+    }
+}
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/EntrypointServiceImpl.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/EntrypointServiceImpl.java
new file mode 100644
index 0000000000..e0b7524860
--- /dev/null
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/EntrypointServiceImpl.java
@@ -0,0 +1,183 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.gravitee.am.service.impl;
+
+import io.gravitee.am.common.audit.EventType;
+import io.gravitee.am.identityprovider.api.User;
+import io.gravitee.am.model.Entrypoint;
+import io.gravitee.am.repository.management.api.EntrypointRepository;
+import io.gravitee.am.service.AuditService;
+import io.gravitee.am.service.EntrypointService;
+import io.gravitee.am.service.exception.EntrypointNotFoundException;
+import io.gravitee.am.service.exception.InvalidEntrypointException;
+import io.gravitee.am.service.model.NewEntrypoint;
+import io.gravitee.am.service.model.UpdateEntrypoint;
+import io.gravitee.am.service.reporter.builder.AuditBuilder;
+import io.gravitee.am.service.reporter.builder.management.EntrypointAuditBuilder;
+import io.gravitee.common.utils.UUID;
+import io.reactivex.Completable;
+import io.reactivex.Flowable;
+import io.reactivex.Single;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Date;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@Component
+public class EntrypointServiceImpl implements EntrypointService {
+
+    private final Logger LOGGER = LoggerFactory.getLogger(EntrypointServiceImpl.class);
+
+    private final EntrypointRepository entrypointRepository;
+
+    private final AuditService auditService;
+
+    public EntrypointServiceImpl(@Lazy EntrypointRepository entrypointRepository,
+                                 AuditService auditService) {
+        this.entrypointRepository = entrypointRepository;
+        this.auditService = auditService;
+    }
+
+    @Override
+    public Single<Entrypoint> findById(String id, String organizationId) {
+
+        LOGGER.debug("Find entrypoint by id {} and organizationId {}", id, organizationId);
+
+        return entrypointRepository.findById(id, organizationId)
+                .switchIfEmpty(Single.error(new EntrypointNotFoundException(id)));
+    }
+
+    @Override
+    public Flowable<Entrypoint> findAll(String organizationId) {
+
+        LOGGER.debug("Find all entrypoints by organizationId {}", organizationId);
+
+        return entrypointRepository.findAll(organizationId);
+    }
+
+    @Override
+    public Single<Entrypoint> create(String organizationId, NewEntrypoint newEntrypoint, User principal) {
+
+        LOGGER.debug("Create a new entrypoint {} for organization {}", newEntrypoint, organizationId);
+
+        Entrypoint toCreate = new Entrypoint();
+        toCreate.setOrganizationId(organizationId);
+        toCreate.setName(newEntrypoint.getName());
+        toCreate.setDescription(newEntrypoint.getDescription());
+        toCreate.setUrl(newEntrypoint.getUrl());
+        toCreate.setTags(newEntrypoint.getTags());
+
+        return createInternal(toCreate, principal);
+    }
+
+    @Override
+    public Single<Entrypoint> createDefault(String organizationId) {
+
+        Entrypoint toCreate = new Entrypoint();
+
+        toCreate.setName("Default");
+        toCreate.setDescription("Default entrypoint");
+        toCreate.setUrl("https://auth.company.com");
+        toCreate.setTags(Collections.emptyList());
+        toCreate.setOrganizationId(organizationId);
+        toCreate.setDefaultEntrypoint(true);
+
+        return createInternal(toCreate, null);
+    }
+
+    @Override
+    public Single<Entrypoint> update(String entrypointId, String organizationId, UpdateEntrypoint updateEntrypoint, User principal) {
+
+        LOGGER.debug("Update an existing entrypoint {}", updateEntrypoint);
+
+        return findById(entrypointId, organizationId)
+                .flatMap(oldEntrypoint -> {
+                    Entrypoint toUpdate = new Entrypoint(oldEntrypoint);
+                    toUpdate.setName(updateEntrypoint.getName());
+                    toUpdate.setDescription(updateEntrypoint.getDescription());
+                    toUpdate.setUrl(updateEntrypoint.getUrl());
+                    toUpdate.setTags(updateEntrypoint.getTags());
+                    toUpdate.setUpdatedAt(new Date());
+
+                    return validate(toUpdate, oldEntrypoint)
+                            .andThen(entrypointRepository.update(toUpdate)
+                                    .doOnSuccess(updated -> auditService.report(AuditBuilder.builder(EntrypointAuditBuilder.class).principal(principal).type(EventType.ENTRYPOINT_UPDATED).entrypoint(updated).oldValue(oldEntrypoint)))
+                                    .doOnError(throwable -> auditService.report(AuditBuilder.builder(EntrypointAuditBuilder.class).principal(principal).type(EventType.ENTRYPOINT_UPDATED).throwable(throwable))));
+                });
+    }
+
+    @Override
+    public Completable delete(String id, String orgaizationId, User principal) {
+
+        LOGGER.debug("Delete entrypoint by id {} and organizationId {}", id, orgaizationId);
+
+        return findById(id, orgaizationId)
+                .flatMapCompletable(entrypoint -> entrypointRepository.delete(id)
+                        .doOnComplete(() -> auditService.report(AuditBuilder.builder(EntrypointAuditBuilder.class).principal(principal).type(EventType.ENTRYPOINT_DELETED).entrypoint(entrypoint)))
+                        .doOnError(throwable -> auditService.report(AuditBuilder.builder(EntrypointAuditBuilder.class).principal(principal).type(EventType.ENTRYPOINT_DELETED).throwable(throwable))));
+    }
+
+    private Single<Entrypoint> createInternal(Entrypoint toCreate, User principal) {
+
+        Date now = new Date();
+
+        toCreate.setId(UUID.random().toString());
+        toCreate.setCreatedAt(now);
+        toCreate.setUpdatedAt(now);
+
+        return validate(toCreate)
+                .andThen(entrypointRepository.create(toCreate)
+                        .doOnSuccess(entrypoint -> auditService.report(AuditBuilder.builder(EntrypointAuditBuilder.class).entrypoint(entrypoint).principal(principal).type(EventType.ENTRYPOINT_CREATED)))
+                        .doOnError(throwable -> auditService.report(AuditBuilder.builder(EntrypointAuditBuilder.class).referenceId(toCreate.getOrganizationId()).principal(principal).type(EventType.ENTRYPOINT_CREATED).throwable(throwable))));
+    }
+
+    private Completable validate(Entrypoint entrypoint) {
+        return validate(entrypoint, null);
+    }
+
+    private Completable validate(Entrypoint entrypoint, Entrypoint oldEntrypoint) {
+
+        if (oldEntrypoint != null && oldEntrypoint.isDefaultEntrypoint()) {
+            // Only the url of the default entrypoint can be updated.
+            if (!entrypoint.getName().equals(oldEntrypoint.getName())
+                    || !entrypoint.getDescription().equals(oldEntrypoint.getDescription())
+                    || !entrypoint.getTags().equals(oldEntrypoint.getTags())) {
+                return Completable.error(new InvalidEntrypointException("Only the url of the default entrypoint can be updated."));
+            }
+        }
+
+        try {
+            // Try to instantiate uri to check if it's a valid endpoint url.
+            URL url = new URL(entrypoint.getUrl());
+            if (!url.getProtocol().equals("http") && !url.getProtocol().equals("https")) {
+                throw new MalformedURLException();
+            }
+
+            return Completable.complete();
+        } catch (MalformedURLException e) {
+            return Completable.error(new InvalidEntrypointException("Entrypoint must have a valid url."));
+        }
+    }
+}
\ No newline at end of file
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/OrganizationServiceImpl.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/OrganizationServiceImpl.java
index a7a49a3cb7..4d38efd36e 100644
--- a/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/OrganizationServiceImpl.java
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/impl/OrganizationServiceImpl.java
@@ -20,6 +20,7 @@ import io.gravitee.am.identityprovider.api.User;
 import io.gravitee.am.model.Organization;
 import io.gravitee.am.repository.management.api.OrganizationRepository;
 import io.gravitee.am.service.AuditService;
+import io.gravitee.am.service.EntrypointService;
 import io.gravitee.am.service.OrganizationService;
 import io.gravitee.am.service.RoleService;
 import io.gravitee.am.service.exception.OrganizationNotFoundException;
@@ -27,6 +28,7 @@ import io.gravitee.am.service.model.NewOrganization;
 import io.gravitee.am.service.model.PatchOrganization;
 import io.gravitee.am.service.reporter.builder.AuditBuilder;
 import io.gravitee.am.service.reporter.builder.management.OrganizationAuditBuilder;
+import io.reactivex.Completable;
 import io.reactivex.Maybe;
 import io.reactivex.Single;
 import org.slf4j.Logger;
@@ -50,13 +52,17 @@ public class OrganizationServiceImpl implements OrganizationService {
 
     private final RoleService roleService;
 
+    private final EntrypointService entrypointService;
+
     private final AuditService auditService;
 
     public OrganizationServiceImpl(@Lazy OrganizationRepository organizationRepository,
                                    RoleService roleService,
+                                   EntrypointService entrypointService,
                                    AuditService auditService) {
         this.organizationRepository = organizationRepository;
         this.roleService = roleService;
+        this.entrypointService = entrypointService;
         this.auditService = auditService;
     }
 
@@ -121,8 +127,10 @@ public class OrganizationServiceImpl implements OrganizationService {
 
         // Creates an organization and set ownership.
         return organizationRepository.create(toCreate)
-                .flatMap(createdOrganization -> roleService.createDefaultRoles(createdOrganization.getId())
-                        .andThen(Single.just(createdOrganization)))
+                .flatMap(createdOrganization ->
+                        Completable.mergeArrayDelayError(entrypointService.createDefault(createdOrganization.getId()).ignoreElement(),
+                                roleService.createDefaultRoles(createdOrganization.getId()))
+                                .andThen(Single.just(createdOrganization)))
                 .doOnSuccess(organization -> auditService.report(AuditBuilder.builder(OrganizationAuditBuilder.class).type(EventType.ORGANIZATION_CREATED).organization(organization).principal(owner)))
                 .doOnError(throwable -> auditService.report(AuditBuilder.builder(OrganizationAuditBuilder.class).type(EventType.ORGANIZATION_CREATED).organization(toCreate).principal(owner).throwable(throwable)));
     }
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/model/NewEntrypoint.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/model/NewEntrypoint.java
new file mode 100644
index 0000000000..f5e5725ad6
--- /dev/null
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/model/NewEntrypoint.java
@@ -0,0 +1,76 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.gravitee.am.service.model;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class NewEntrypoint {
+
+    @NotNull
+    private String name;
+
+    private String description;
+
+    @NotNull
+    private String url;
+
+    @NotNull
+    private List<String> tags;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    @Override
+    public String toString() {
+        return "NewTag{" +
+                "name='" + name + '\'' +
+                '}';
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    public List<String> getTags() {
+        return tags;
+    }
+
+    public void setTags(List<String> tags) {
+        this.tags = tags;
+    }
+}
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/model/UpdateEntrypoint.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/model/UpdateEntrypoint.java
new file mode 100644
index 0000000000..2c354a8c8c
--- /dev/null
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/model/UpdateEntrypoint.java
@@ -0,0 +1,69 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.gravitee.am.service.model;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class UpdateEntrypoint {
+
+    @NotNull
+    private String name;
+
+    private String description;
+
+    @NotNull
+    private String url;
+
+    @NotNull
+    private List<String> tags;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(String url) {
+        this.url = url;
+    }
+
+    public List<String> getTags() {
+        return tags;
+    }
+
+    public void setTags(List<String> tags) {
+        this.tags = tags;
+    }
+}
diff --git a/gravitee-am-service/src/main/java/io/gravitee/am/service/reporter/builder/management/EntrypointAuditBuilder.java b/gravitee-am-service/src/main/java/io/gravitee/am/service/reporter/builder/management/EntrypointAuditBuilder.java
new file mode 100644
index 0000000000..2e4613c718
--- /dev/null
+++ b/gravitee-am-service/src/main/java/io/gravitee/am/service/reporter/builder/management/EntrypointAuditBuilder.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.gravitee.am.service.reporter.builder.management;
+
+import io.gravitee.am.common.audit.EntityType;
+import io.gravitee.am.common.audit.EventType;
+import io.gravitee.am.model.Entrypoint;
+import io.gravitee.am.model.ReferenceType;
+import io.gravitee.am.model.Tag;
+
+/**
+ * @author David BRASSELY (david.brassely at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class EntrypointAuditBuilder extends ManagementAuditBuilder<EntrypointAuditBuilder> {
+
+    public EntrypointAuditBuilder() {
+        // Entrypoints are managed at organization level.
+        referenceType(ReferenceType.ORGANIZATION);
+    }
+
+    public EntrypointAuditBuilder entrypoint(Entrypoint entrypoint) {
+        if (EventType.ENTRYPOINT_CREATED.equals(getType()) || EventType.ENTRYPOINT_UPDATED.equals(getType())) {
+            setNewValue(entrypoint);
+        }
+
+        referenceId(entrypoint.getOrganizationId());
+
+        setTarget(entrypoint.getId(), EntityType.ENTRYPOINT, null, entrypoint.getName(), ReferenceType.ORGANIZATION, entrypoint.getOrganizationId());
+        return this;
+    }
+}
diff --git a/gravitee-am-ui/src/app/app-routing.module.ts b/gravitee-am-ui/src/app/app-routing.module.ts
index 4d6f591e43..5abf2b9129 100644
--- a/gravitee-am-ui/src/app/app-routing.module.ts
+++ b/gravitee-am-ui/src/app/app-routing.module.ts
@@ -47,6 +47,7 @@ import {LogoutCallbackComponent} from './logout/callback/callback.component';
 import {LogoutComponent} from './logout/logout.component';
 import {DomainsResolver} from './resolvers/domains.resolver';
 import {DomainResolver} from './resolvers/domain.resolver';
+import {DomainEntrypointResolver} from "./resolvers/domain-entrypoint.resolver";
 import {DomainPermissionsResolver} from './resolvers/domain-permissions.resolver';
 import {ProvidersResolver} from './resolvers/providers.resolver';
 import {ProviderResolver} from './resolvers/provider.resolver';
@@ -117,6 +118,7 @@ import {ApplicationsResolver} from './resolvers/applications.resolver';
 import {ApplicationCreationComponent} from './domain/applications/creation/application-creation.component';
 import {ApplicationComponent} from './domain/applications/application/application.component';
 import {ApplicationOverviewComponent} from './domain/applications/application/overview/overview.component';
+import {ApplicationEndpointsComponent} from './domain/applications/application/endpoints/endpoints.component';
 import {ApplicationResolver} from './resolvers/application.resolver';
 import {ApplicationPermissionsResolver} from './resolvers/application-permissions.resolver';
 import {ApplicationIdPComponent} from './domain/applications/application/idp/idp.component';
@@ -146,6 +148,11 @@ import {FactorComponent} from './domain/settings/factors/factor/factor.component
 import {FactorResolver} from './resolvers/factor.resolver';
 import {EnrolledFactorsResolver} from './resolvers/enrolled-factors.resolver';
 import {NotFoundComponent} from './not-found/not-found.component';
+import {EntrypointsComponent} from "./settings/management/entrypoints/entrypoints.component";
+import {EntrypointCreationComponent} from "./settings/management/entrypoints/creation/entrypoint-creation.component";
+import {EntrypointComponent} from "./settings/management/entrypoints/entrypoint/entrypoint.component";
+import {EntrypointResolver} from "./resolvers/entrypoint.resolver";
+import {EntrypointsResolver} from "./resolvers/entrypoints.resolver";
 
 const routes: Routes = [
   {
@@ -465,6 +472,45 @@ const routes: Routes = [
                 only: ['organization_tag_read']
               }
             }
+          },
+          { path: 'entrypoints',
+            component: EntrypointsComponent,
+            canActivate: [AuthGuard],
+            resolve: {
+              entrypoints: EntrypointsResolver
+            },
+            data: {
+              menu: {
+                label: 'Entrypoints',
+                section: 'Deployment'
+              },
+              perms: {
+                only: ['organization_entrypoint_list']
+              }
+            }
+          },
+          { path: 'entrypoints/new',
+            component: EntrypointCreationComponent,
+            canActivate: [AuthGuard],
+            data: {
+              perms: {
+                only: ['organization_entrypoint_create']
+              }
+            }
+          },
+          {
+            path: 'entrypoints/:entrypointId',
+            component: EntrypointComponent,
+            canActivate: [AuthGuard],
+            resolve: {
+              entrypoint: EntrypointResolver,
+                tags: TagsResolver
+            },
+            data: {
+              perms: {
+                only: ['organization_entrypoint_read']
+              }
+            }
           }
         ]
       }
@@ -540,7 +586,17 @@ const routes: Routes = [
         },
         children: [
           { path: '', redirectTo: 'overview', pathMatch: 'full' },
-          { path: 'overview', component: ApplicationOverviewComponent, resolve: { domain: DomainResolver } },
+          { path: 'overview', component: ApplicationOverviewComponent,
+            resolve: {
+              domain: DomainResolver,
+              entrypoint: DomainEntrypointResolver
+            }
+          },{ path: 'endpoints', component: ApplicationEndpointsComponent,
+            resolve: {
+              domain: DomainResolver,
+              entrypoint: DomainEntrypointResolver
+            }
+          },
           { path: 'idp',
             component: ApplicationIdPComponent,
             canActivate: [AuthGuard],
diff --git a/gravitee-am-ui/src/app/app.module.ts b/gravitee-am-ui/src/app/app.module.ts
index 8cb3251bc4..1acd1fd5cd 100644
--- a/gravitee-am-ui/src/app/app.module.ts
+++ b/gravitee-am-ui/src/app/app.module.ts
@@ -75,6 +75,7 @@ import { LogoutComponent } from './logout/logout.component';
 import { LogoutCallbackComponent } from './logout/callback/callback.component';
 import { DomainsResolver } from './resolvers/domains.resolver';
 import { DomainResolver } from './resolvers/domain.resolver';
+import {DomainEntrypointResolver} from "./resolvers/domain-entrypoint.resolver";
 import { DomainDashboardComponent } from './domain/dashboard/dashboard.component';
 import { DomainSettingsComponent } from './domain/settings/settings.component';
 import { DomainSettingsGeneralComponent } from './domain/settings/general/general.component';
@@ -195,6 +196,12 @@ import { TagService } from './services/tag.service';
 import { TagsComponent } from './settings/management/tags/tags.component';
 import { TagCreationComponent } from './settings/management/tags/creation/tag-creation.component';
 import { TagComponent } from './settings/management/tags/tag/tag.component';
+import { EntrypointsResolver } from './resolvers/entrypoints.resolver';
+import { EntrypointResolver } from './resolvers/entrypoint.resolver';
+import { EntrypointService } from './services/entrypoint.service';
+import { EntrypointsComponent } from './settings/management/entrypoints/entrypoints.component';
+import { EntrypointCreationComponent } from './settings/management/entrypoints/creation/entrypoint-creation.component';
+import { EntrypointComponent } from './settings/management/entrypoints/entrypoint/entrypoint.component';
 import { AccountSettingsComponent } from './domain/components/account/account-settings.component';
 import { HttpRequestInterceptor } from './interceptors/http-request.interceptor';
 import { PolicyFormComponent } from './domain/settings/policies/policy/form/form.component';
@@ -211,6 +218,7 @@ import { ApplicationCreationStep1Component } from './domain/applications/creatio
 import { ApplicationCreationStep2Component } from './domain/applications/creation/steps/step2/step2.component';
 import { ApplicationComponent } from './domain/applications/application/application.component';
 import { ApplicationOverviewComponent } from './domain/applications/application/overview/overview.component';
+import { ApplicationEndpointsComponent } from './domain/applications/application/endpoints/endpoints.component';
 import { ApplicationGeneralComponent } from './domain/applications/application/advanced/general/general.component';
 import { ApplicationIdPComponent } from './domain/applications/application/idp/idp.component';
 import { ApplicationDesignComponent } from './domain/applications/application/design/design.component';
@@ -375,6 +383,9 @@ import { NotFoundComponent } from './not-found/not-found.component';
     TagsComponent,
     TagCreationComponent,
     TagComponent,
+    EntrypointsComponent,
+    EntrypointCreationComponent,
+    EntrypointComponent,
     AccountSettingsComponent,
     PolicyFormComponent,
     PoliciesInfoDialog,
@@ -387,6 +398,7 @@ import { NotFoundComponent } from './not-found/not-found.component';
     ApplicationCreationStep2Component,
     ApplicationComponent,
     ApplicationOverviewComponent,
+    ApplicationEndpointsComponent,
     ApplicationGeneralComponent,
     ApplicationIdPComponent,
     ApplicationDesignComponent,
@@ -455,6 +467,7 @@ import { NotFoundComponent } from './not-found/not-found.component';
     AppConfig,
     DomainsResolver,
     DomainResolver,
+    DomainEntrypointResolver,
     ProvidersResolver,
     ProviderResolver,
     CertificatesResolver,
@@ -488,6 +501,9 @@ import { NotFoundComponent } from './not-found/not-found.component';
     TagService,
     TagsResolver,
     TagResolver,
+    EntrypointService,
+    EntrypointsResolver,
+    EntrypointResolver,
     PolicyService,
     PoliciesResolver,
     ApplicationService,
diff --git a/gravitee-am-ui/src/app/domain/applications/application/application.component.ts b/gravitee-am-ui/src/app/domain/applications/application/application.component.ts
index c04b64b6b2..d59ad786ec 100644
--- a/gravitee-am-ui/src/app/domain/applications/application/application.component.ts
+++ b/gravitee-am-ui/src/app/domain/applications/application/application.component.ts
@@ -30,6 +30,7 @@ export class ApplicationComponent implements OnInit {
   logoUrl: string;
   navLinks: any = [
     {'href': 'overview' , 'label': 'Overview', 'icon': 'more_vert'},
+    {'href': 'endpoints' , 'label': 'Endpoints', 'icon': 'transform'},
     {'href': 'idp' , 'label': 'Identity Providers', 'icon': 'swap_horiz'},
     {'href': 'design' , 'label': 'Design', 'icon': 'palette'},
     {'href': 'settings', 'label': 'Settings', 'icon': 'settings'}
diff --git a/gravitee-am-ui/src/app/domain/applications/application/endpoints/endpoints.component.html b/gravitee-am-ui/src/app/domain/applications/application/endpoints/endpoints.component.html
new file mode 100644
index 0000000000..0b38a926bb
--- /dev/null
+++ b/gravitee-am-ui/src/app/domain/applications/application/endpoints/endpoints.component.html
@@ -0,0 +1,83 @@
+<!--
+
+    Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+            http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<div>
+  <div class="gv-form-section" fxFlex="70">
+    <div class="gv-form-section-title">
+      <h5>Endpoints</h5>
+      <mat-divider></mat-divider>
+    </div>
+    <div>
+      <mat-form-field appearance="outline" floatLabel="always">
+        <mat-label>Authorization endpoint</mat-label>
+        <input matInput type="text" name="authorizeEndpoint" [value]="endpoint('/oauth/authorize')" disabled>
+        <mat-hint>The authorization endpoint is used to obtain an OAuth 2.0 token using either authorization code or implicit grant type</mat-hint>
+        <button mat-icon-button matSuffix matTooltip="Copy to clipboard" ngxClipboard [cbContent]="endpoint('/oauth/authorize')" (cbOnSuccess)="valueCopied('Copied to clipboard')" (click)="$event.preventDefault();"><mat-icon>content_copy</mat-icon></button>
+      </mat-form-field>
+      <mat-form-field appearance="outline" floatLabel="always">
+        <mat-label>Token endpoint</mat-label>
+        <input matInput type="text" name="tokenEndpoint" [value]="endpoint('/oauth/token')" disabled>
+        <mat-hint>The token endpoint is used to obtain an OAuth 2.0 token</mat-hint>
+        <button mat-icon-button matSuffix matTooltip="Copy to clipboard" ngxClipboard [cbContent]="endpoint('/oauth/token')" (cbOnSuccess)="valueCopied('Copied to clipboard')" (click)="$event.preventDefault();"><mat-icon>content_copy</mat-icon></button>
+      </mat-form-field>
+      <mat-form-field appearance="outline" floatLabel="always">
+        <mat-label>Introspection endpoint</mat-label>
+        <input matInput type="text" name="introspectEndpoint" [value]="endpoint('/oauth/introspect')" disabled>
+        <mat-hint>The introspection endpoint allows to access information about an OAuth 2.0 token</mat-hint>
+        <button mat-icon-button matSuffix matTooltip="Copy to clipboard" ngxClipboard [cbContent]="endpoint('/oauth/introspect')" (cbOnSuccess)="valueCopied('Copied to clipboard')" (click)="$event.preventDefault();"><mat-icon>content_copy</mat-icon></button>
+      </mat-form-field>
+      <mat-form-field appearance="outline" floatLabel="always">
+        <mat-label>Revocation endpoint</mat-label>
+        <input matInput type="text" name="revokeEndpoint" [value]="endpoint('/oauth/revoke')" disabled>
+        <mat-hint> The revocation endpoint allows to invalidate a previously obtained OAuth 2.0 refresh or access token</mat-hint>
+        <button mat-icon-button matSuffix matTooltip="Copy to clipboard" ngxClipboard [cbContent]="endpoint('/oauth/revoke')" (cbOnSuccess)="valueCopied('Copied to clipboard')" (click)="$event.preventDefault();"><mat-icon>content_copy</mat-icon></button>
+      </mat-form-field>
+      <mat-form-field appearance="outline" floatLabel="always">
+        <mat-label>UserInfo endpoint</mat-label>
+        <input matInput type="text" name="userInfoEndpoint" [value]="endpoint('/oidc/userinfo')" disabled>
+        <mat-hint>The authorization endpoint is used to obtain an OAuth 2.0 token using either authorization code or implicit grant type</mat-hint>
+        <button mat-icon-button matSuffix matTooltip="Copy to clipboard" ngxClipboard [cbContent]="endpoint('/oidc/userinfo')" (cbOnSuccess)="valueCopied('Copied to clipboard')" (click)="$event.preventDefault();"><mat-icon>content_copy</mat-icon></button>
+      </mat-form-field>
+      <mat-form-field appearance="outline" floatLabel="always">
+        <mat-label>End session endpoint</mat-label>
+        <input matInput type="text" name="endSessionEndpoint" [value]="endpoint('/logout')" disabled>
+        <mat-hint>The end session endpoint allows to invalidate a user's session</mat-hint>
+        <button mat-icon-button matSuffix matTooltip="Copy to clipboard" ngxClipboard [cbContent]="endpoint('/logout')" (cbOnSuccess)="valueCopied('Copied to clipboard')" (click)="$event.preventDefault();"><mat-icon>content_copy</mat-icon></button>
+      </mat-form-field>
+      <mat-form-field appearance="outline" floatLabel="always">
+        <mat-label>OpenID Configuration endpoint</mat-label>
+        <input matInput type="text" name="openIdConfigurationEndpoint" [value]="endpoint('/oidc/.well-known/openid-configuration')" disabled>
+        <mat-hint>The OpenId configuration endpoint allows third party systems to get the complete OpenId configuration</mat-hint>
+        <button mat-icon-button matSuffix matTooltip="Copy to clipboard" ngxClipboard [cbContent]="endpoint('/oidc/.well-known/openid-configuration')" (cbOnSuccess)="valueCopied('Copied to clipboard')" (click)="$event.preventDefault();"><mat-icon>content_copy</mat-icon></button>
+      </mat-form-field>
+      <mat-form-field appearance="outline" floatLabel="always">
+        <mat-label>SCIM Configuration endpoint</mat-label>
+        <input matInput type="text" name="scimConfigurationEndpoint" [value]="endpoint('/scim/ServiceProviderConfig')" disabled>
+        <mat-hint>The SCIM configuration endpoint allows third party systems to get the complete SCIM configuration</mat-hint>
+        <button mat-icon-button matSuffix matTooltip="Copy to clipboard" ngxClipboard [cbContent]="endpoint('/scim/ServiceProviderConfig')" (cbOnSuccess)="valueCopied('Copied to clipboard')" (click)="$event.preventDefault();"><mat-icon>content_copy</mat-icon></button>
+      </mat-form-field>
+    </div>
+  </div>
+  <div class="gv-page-description" fxFlex>
+    <h3>Useful endpoints</h3>
+    <div class="gv-page-description-content">
+      <p>
+        All necessary endpoints to communicate with the Authorization Server.
+      </p>
+    </div>
+  </div>
+</div>
diff --git a/gravitee-am-ui/src/app/domain/applications/application/endpoints/endpoints.component.scss b/gravitee-am-ui/src/app/domain/applications/application/endpoints/endpoints.component.scss
new file mode 100644
index 0000000000..7b2f2869cb
--- /dev/null
+++ b/gravitee-am-ui/src/app/domain/applications/application/endpoints/endpoints.component.scss
@@ -0,0 +1,17 @@
+mat-form-field {
+  margin-top: 10px;
+}
+input {
+  font-family: monospace;
+}
+
+:host {
+  ::ng-deep {
+    .mat-form-field-appearance-outline.mat-form-field-disabled .mat-form-field-outline {
+      background: #f5f5f5;
+    }
+    .mat-input-element:disabled {
+      color: rgba(0, 0, 0, 0.87);
+    }
+  }
+}
diff --git a/gravitee-am-ui/src/app/domain/applications/application/endpoints/endpoints.component.spec.ts b/gravitee-am-ui/src/app/domain/applications/application/endpoints/endpoints.component.spec.ts
new file mode 100644
index 0000000000..59e546e95e
--- /dev/null
+++ b/gravitee-am-ui/src/app/domain/applications/application/endpoints/endpoints.component.spec.ts
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import {ApplicationEndpointsComponent} from './endpoints.component';
+
+describe('ApplicationEndpointsComponent', () => {
+  let component: ApplicationEndpointsComponent;
+  let fixture: ComponentFixture<ApplicationEndpointsComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ ApplicationEndpointsComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(ApplicationEndpointsComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/gravitee-am-ui/src/app/domain/applications/application/endpoints/endpoints.component.ts b/gravitee-am-ui/src/app/domain/applications/application/endpoints/endpoints.component.ts
new file mode 100644
index 0000000000..29a1fd97db
--- /dev/null
+++ b/gravitee-am-ui/src/app/domain/applications/application/endpoints/endpoints.component.ts
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {Component, ElementRef, OnInit, ViewChild} from '@angular/core';
+import {ActivatedRoute} from '@angular/router';
+import {AuthService} from "../../../../services/auth.service";
+import {SnackbarService} from "../../../../services/snackbar.service";
+
+@Component({
+  selector: 'application-overview',
+  templateUrl: './endpoints.component.html',
+  styleUrls: ['./endpoints.component.scss']
+})
+export class ApplicationEndpointsComponent implements OnInit {
+  application: any;
+  entrypoint: any;
+  private baseURL: string;
+  @ViewChild('copyText', { read: ElementRef }) copyText: ElementRef;
+
+  constructor(private route: ActivatedRoute,
+              private snackbarService: SnackbarService) {
+  }
+
+  ngOnInit() {
+    this.entrypoint = this.route.snapshot.data['entrypoint'];
+    this.application = this.route.snapshot.parent.data['application'];
+    this.baseURL = this.entrypoint.url + '/' + this.application.domain;
+  }
+
+  valueCopied(message: string) {
+    this.snackbarService.open(message);
+  }
+
+  endpoint(path) {
+    return this.baseURL + path;
+  }
+
+}
diff --git a/gravitee-am-ui/src/app/domain/applications/application/overview/overview.component.html b/gravitee-am-ui/src/app/domain/applications/application/overview/overview.component.html
index 4f6eb1f632..8eea00b122 100644
--- a/gravitee-am-ui/src/app/domain/applications/application/overview/overview.component.html
+++ b/gravitee-am-ui/src/app/domain/applications/application/overview/overview.component.html
@@ -49,7 +49,14 @@
           <p>
             To set up authentication in your application and display to users the login page, you have to redirect your users to the following URL :
           </p>
-          <pre>https://auth.mycompany.com/{{application.domain}}/oauth/authorize?client_id={{clientId}}&response_type=code&redirect_uri={{redirectUri}}</pre>
+
+          <div class="code">
+            <button mat-icon-button matSuffix matTooltip="Copy to clipboard" (click)="copyToClipboard(pre_authorize);">
+              <mat-icon>content_copy</mat-icon>
+            </button>
+            <pre #pre_authorize>{{entrypoint.url}}/{{application.domain}}/oauth/authorize?client_id={{clientId}}&response_type={{getAuthorizationFlowResponseType()}}&redirect_uri={{redirectUri}}</pre>
+          </div>
+
           <p>Users will be redirected to the login page and will start the Authentication Flow.</p>
           <blockquote>
             <p>
@@ -57,7 +64,14 @@
             </p>
           </blockquote>
           <p>Once users have been authenticated they will be redirected to your application URL :</p>
-          <pre>{{redirectUri}}</pre>
+
+          <div class="code">
+            <button mat-icon-button matSuffix matTooltip="Copy to clipboard" (click)="copyToClipboard(pre_redirect_uri);">
+              <mat-icon>content_copy</mat-icon>
+            </button>
+            <pre #pre_redirect_uri>{{redirectUri}}</pre>
+          </div>
+
           <p>Either with :</p>
           <ul>
             <li>an <b>authorization_code</b></li>
@@ -68,33 +82,58 @@
               An extra step is required if the response_type=code, exchange the authorization_code for an access_token.
             </p>
           </blockquote>
-          <pre>curl -X POST \
-          https://auth.mycompany.com/{{application.domain}}/oauth/token \
-          -H 'Content-Type: application/x-www-form-urlencoded' \
-          -d 'grant_type=authorization_code&code=|code|&redirect_uri={{redirectUri}}&client_id={{clientId}}'
-          </pre>
+          <div class="code">
+            <button mat-icon-button matSuffix matTooltip="Copy to clipboard" (click)="copyToClipboard(pre_authorization_code_exchange, authorizationHeader);">
+              <mat-icon>content_copy</mat-icon>
+            </button>
+            <button *ngIf="!isHidden(safeAuthorizationHeader)" mat-icon-button matSuffix matTooltip="Hide sensitive data" (click)="safeAuthorizationHeader = hidden;">
+              <mat-icon>visibility_off</mat-icon>
+            </button>
+            <button *ngIf="isHidden(safeAuthorizationHeader)" mat-icon-button matSuffix matTooltip="Reveal sensitive data" (click)="safeAuthorizationHeader = authorizationHeader">
+              <mat-icon>visibility</mat-icon>
+            </button>
+
+            <pre #pre_authorization_code_exchange class="multiline">curl -X POST \
+  {{entrypoint.url}}/{{application.domain}}/oauth/token \
+  -H 'Authorization: Basic {{safeAuthorizationHeader}}' \
+  -H 'Content-Type: application/x-www-form-urlencoded' \
+  -d 'grant_type=authorization_code&code=|code|&redirect_uri={{redirectUri}}&client_id={{clientId}}'</pre>
+          </div>
+
           <h2>Get user information</h2>
           <p>The user's profile information can be extracted from the ID token or by calling the following URL :</p>
-          <pre>curl -X GET \
-          https://auth.mycompany.com/{{application.domain}}/oidc/userinfo \
-          -H 'Authorization: Bearer access_token'
-          </pre>
+          <div class="code">
+            <button mat-icon-button matSuffix matTooltip="Copy to clipboard" (click)="copyToClipboard(pre_userinfo);">
+              <mat-icon>content_copy</mat-icon>
+            </button>
+            <pre #pre_userinfo class="multiline">curl -X GET \
+  {{entrypoint.url}}/{{application.domain}}/oidc/userinfo \
+  -H 'Authorization: Bearer access_token'</pre>
+          </div>
           <p>
             The profile information is used to display their name and hide/show some UI actions according to their permissions.
           </p>
+
           <h2>Call your APIs</h2>
           <p>
             Most of your applications require data from APIs. Some of those resources have restricted access, so that only authenticated users with sufficient privileges can access them.
           </p>
-          <pre>curl -X GET \
-          https://api.mycompany.com/api/v1/data \
-          -H 'Authorization: Bearer access_token'
-          </pre>
+          <div class="code">
+            <pre class="multiline">curl -X GET \
+  https://api.mycompany.com/api/v1/data \
+  -H 'Authorization: Bearer access_token'</pre>
+          </div>
+
           <h2>Log the User Out</h2>
           <p>
             Any authenticated users who want to invalidate their session can navigate to the following URL :
           </p>
-          <pre>https://auth.mycompany.com/{{application.domain}}/logout</pre>
+          <div class="code">
+            <button mat-icon-button matSuffix matTooltip="Copy to clipboard" (click)="copyToClipboard(pre_logout);">
+              <mat-icon>content_copy</mat-icon>
+            </button>
+            <pre #pre_logout>{{entrypoint.url}}/{{application.domain}}/logout</pre>
+          </div>
           <blockquote>
             <p>
               By default access tokens and refresh tokens are not revoked. You can add the invalidate_tokens=true query parameter to the request to invalidate current user tokens.
@@ -109,22 +148,37 @@
         <div *ngIf="isServiceApp()">
           <h1>Machine to machine interaction</h1>
           <p>A Service application represents a program that interacts with APIs where there is no user involved.</p>
+
           <h2>Getting an access token</h2>
           <p>You can request an access token using the OAuth 2.0 client credentials grant type.</p>
-          <pre>curl -X POST \
-          https://auth.mycompany.com/{{application.domain}}/oauth/token \
-          -H 'Content-Type: application/x-www-form-urlencoded' \
-          -d 'grant_type=client_credentials&client_id={{clientId}}&client_secret={{clientSecret}}'
-          </pre>
+          <div class="code">
+            <button mat-icon-button matSuffix matTooltip="Copy to clipboard" (click)="copyToClipboard(pre_token, clientSecret);">
+              <mat-icon>content_copy</mat-icon>
+            </button>
+            <button *ngIf="!isHidden(safeClientSecret)" mat-icon-button matSuffix matTooltip="Hide sensitive data" (click)="safeClientSecret = hidden;">
+              <mat-icon>visibility_off</mat-icon>
+            </button>
+            <button *ngIf="isHidden(safeClientSecret)" mat-icon-button matSuffix matTooltip="Reveal sensitive data" (click)="safeClientSecret = clientSecret">
+              <mat-icon>visibility</mat-icon>
+            </button>
+            <pre #pre_token class="multiline">curl -X POST \
+  {{entrypoint.url}}/{{application.domain}}/oauth/token \
+  -H 'Content-Type: application/x-www-form-urlencoded' \
+  -d 'grant_type=client_credentials&client_id={{clientId}}&client_secret={{safeClientSecret}}'</pre>
+          </div>
           <p>If the request is valid, the authorization server will issue an access token to make authorized calls to the APIs.</p>
+
           <h2>Call your APIs</h2>
           <p>Use your access_token in your request via the Authorization HTTP header to obtain authorized access to the APIs.</p>
-          <pre>curl -X GET \
-          https://api.mycompany.com/api/v1/data \
-          -H 'Authorization: Bearer access_token'
-          </pre>
+          <div class="code">
+            <pre class="multiline">curl -X GET \
+  https://api.mycompany.com/api/v1/data \
+  -H 'Authorization: Bearer access_token'
+            </pre>
+          </div>
         </div>
       </div>
     </div>
   </div>
+  <textarea name="copyText" #copyText id="" style="opacity: 0;height: 0; width: 0; margin: 0; border: 0"></textarea>
 </div>
diff --git a/gravitee-am-ui/src/app/domain/applications/application/overview/overview.component.scss b/gravitee-am-ui/src/app/domain/applications/application/overview/overview.component.scss
index b39c23833f..1509e41ede 100644
--- a/gravitee-am-ui/src/app/domain/applications/application/overview/overview.component.scss
+++ b/gravitee-am-ui/src/app/domain/applications/application/overview/overview.component.scss
@@ -47,13 +47,4 @@
       margin: 0;
     }
   }
-  pre {
-    overflow: auto;
-    white-space: pre;
-    background: #f5f5f5!important;
-    border-color: transparent!important;
-    margin-bottom: 24px;
-    padding: 13px;
-    font-size: 1em;
-  }
 }
diff --git a/gravitee-am-ui/src/app/domain/applications/application/overview/overview.component.ts b/gravitee-am-ui/src/app/domain/applications/application/overview/overview.component.ts
index 401bf280d8..4499d86ccd 100644
--- a/gravitee-am-ui/src/app/domain/applications/application/overview/overview.component.ts
+++ b/gravitee-am-ui/src/app/domain/applications/application/overview/overview.component.ts
@@ -13,11 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {Component, OnInit} from '@angular/core';
+import {Component, ElementRef, OnInit, ViewChild} from '@angular/core';
 import {ActivatedRoute} from '@angular/router';
-import * as _ from 'lodash';
-import {SnackbarService} from "../../../../services/snackbar.service";
 import {AuthService} from "../../../../services/auth.service";
+import {SnackbarService} from "../../../../services/snackbar.service";
 
 @Component({
   selector: 'application-overview',
@@ -28,31 +27,68 @@ export class ApplicationOverviewComponent implements OnInit {
   domain: any;
   application: any;
   redirectUri: string;
+  grantTypes: string[] = [];
   clientId: string;
   clientSecret: string;
+  safeClientSecret: string;
+  hidden: string = '********';
+  safeAuthorizationHeader: string;
+  authorizationHeader: string;
+  entrypoint: any;
+  @ViewChild('copyText', { read: ElementRef }) copyText: ElementRef;
 
   constructor(private route: ActivatedRoute,
-              private authService: AuthService) {
+              private authService: AuthService,
+              private snackbarService: SnackbarService) {
   }
 
   ngOnInit() {
     this.domain = this.route.snapshot.data['domain'];
+    this.entrypoint = this.route.snapshot.data['entrypoint'];
     this.application = this.route.snapshot.parent.data['application'];
-
+    this.safeClientSecret = this.hidden;
+    this.safeAuthorizationHeader = this.hidden;
     var applicationOAuthSettings = this.application.settings == null ? {} : this.application.settings.oauth || {};
 
     if (this.authService.hasPermissions(['application_openid_read'])) {
+      this.grantTypes = applicationOAuthSettings.grantTypes;
       this.clientId = applicationOAuthSettings.clientId;
       this.clientSecret = applicationOAuthSettings.clientSecret;
       this.redirectUri = applicationOAuthSettings.redirectUris && applicationOAuthSettings.redirectUris[0] !== undefined ? applicationOAuthSettings.redirectUris[0] : 'Not defined' ;
+      this.authorizationHeader = btoa(this.clientId + ':' + this.clientSecret);
     } else {
       this.clientId = 'Insufficient permission';
       this.clientSecret = 'Insufficient permission';
       this.redirectUri = 'Insufficient permission';
+      this.authorizationHeader = 'Insufficient permission';
     }
   }
 
   isServiceApp(): boolean {
     return this.application.type.toLowerCase() === 'service';
   }
+
+  valueCopied(message: string) {
+    this.snackbarService.open(message);
+  }
+
+  copyToClipboard(element: HTMLElement, sensitiveReplacement?) {
+
+    this.copyText.nativeElement.value = element.textContent.replace(this.hidden, sensitiveReplacement ? sensitiveReplacement : '');;
+    this.copyText.nativeElement.select();
+    document.execCommand('copy');
+    this.valueCopied('Copied to clipboard');
+  }
+
+  isHidden(value): boolean {
+    return value === this.hidden;
+  }
+
+  getAuthorizationFlowResponseType(): string {
+    if(this.grantTypes.includes('authorization_code')) {
+      return 'code';
+    } else {
+      return 'token';
+    }
+  }
 }
diff --git a/gravitee-am-ui/src/app/domain/settings/audits/audits.component.ts b/gravitee-am-ui/src/app/domain/settings/audits/audits.component.ts
index 4a8dffb1f3..0579568fba 100644
--- a/gravitee-am-ui/src/app/domain/settings/audits/audits.component.ts
+++ b/gravitee-am-ui/src/app/domain/settings/audits/audits.component.ts
@@ -162,7 +162,7 @@ export class AuditsComponent implements OnInit {
   getTargetUrl(row) {
     let routerLink = [];
 
-    if(row.target.type === 'MEMBERSHIP') {
+    if (row.target.type === 'MEMBERSHIP') {
       // Membership doesn't have link;
       return routerLink;
     }
@@ -173,27 +173,29 @@ export class AuditsComponent implements OnInit {
     } else {
       routerLink.push('/domains');
       routerLink.push(row.target.referenceId);
+
       if (row.target.type !== 'CLIENT' && row.target.type !== 'APPLICATION') {
         routerLink.push('settings');
       }
+    }
 
-      if (row.target.type !== 'DOMAIN') {
-        if (row.target.type !== 'IDENTITY_PROVIDER') {
-          if (row.target.type === 'CLIENT') {
-            routerLink.push('applications');
-          } else {
-            routerLink.push(row.target.type.toLowerCase() + 's');
-          }
-        } else {
-          routerLink.push('providers');
-        }
-        if (row.target.type === 'FORM' || row.target.type === 'EMAIL') {
-          routerLink.push(row.target.type.toLowerCase());
+    if (row.target.type !== 'DOMAIN') {
+      if (row.target.type !== 'IDENTITY_PROVIDER') {
+        if (row.target.type === 'CLIENT') {
+          routerLink.push('applications');
         } else {
-          routerLink.push(row.target.id);
+          routerLink.push(row.target.type.toLowerCase() + 's');
         }
+      } else {
+        routerLink.push('providers');
+      }
+      if (row.target.type === 'FORM' || row.target.type === 'EMAIL') {
+        routerLink.push(row.target.type.toLowerCase());
+      } else {
+        routerLink.push(row.target.id);
       }
     }
+
     return routerLink;
   }
 
diff --git a/gravitee-am-ui/src/app/domain/settings/general/general.component.ts b/gravitee-am-ui/src/app/domain/settings/general/general.component.ts
index 90ea670bf6..f67bc6fe54 100644
--- a/gravitee-am-ui/src/app/domain/settings/general/general.component.ts
+++ b/gravitee-am-ui/src/app/domain/settings/general/general.component.ts
@@ -54,39 +54,36 @@ export class DomainSettingsGeneralComponent implements OnInit {
 
   ngOnInit() {
     this.domain = this.route.snapshot.parent.data['domain'];
-    this.tags = this.route.snapshot.data['tags'];
+
+    if(this.domain.tags === undefined) {
+      this.domain.tags = [];
+    }
+
     this.readonly = !this.authService.hasPermissions(['domain_settings_update']);
     this.initTags();
   }
 
   initTags() {
-    let that = this;
-    // Merge with existing tag
-    this.selectedTags = _.map(this.domain.tags, function(t) {
-      let tag = _.find(that.tags, { 'id': t });
-      if (tag !== undefined) {
-        return tag;
-      }
-
-      return undefined;
-    });
-
-    this.tags = _.difference(this.tags, this.selectedTags);
+    let tags = this.route.snapshot.data['tags'];
+    this.selectedTags = this.domain.tags.map(t => _.find(tags, { 'id': t })).filter(t => typeof t !== 'undefined');
+    this.tags = _.difference(tags, this.selectedTags);
   }
 
   addTag(event) {
     this.selectedTags = this.selectedTags.concat(_.remove(this.tags, { 'id': event.option.value }));
-    this.chipInput['nativeElement'].blur();
-    this.formChanged = true;
+    this.tagsChanged();
   }
 
-  removeTag(permission) {
-    this.tags = this.tags.concat(_.remove(this.selectedTags, function(selectPermission) {
-      return selectPermission.key === permission.key;
-    }));
+  removeTag(tag) {
+    this.selectedTags = this.selectedTags.filter(t => t.id !== tag.id);
+    this.tags.push(tag);
+    this.tagsChanged();
+  }
 
+  tagsChanged() {
     this.chipInput['nativeElement'].blur();
     this.formChanged = true;
+    this.domain.tags = _.map(this.selectedTags, tag => tag.id);
   }
 
   enableDomain(event) {
@@ -95,8 +92,6 @@ export class DomainSettingsGeneralComponent implements OnInit {
   }
 
   update() {
-    this.domain.tags = _.map(this.selectedTags, tag => tag.id);
-
     this.domainService.patchGeneralSettings(this.domain.id, this.domain).subscribe(response => {
       this.domain = response;
       this.domainService.notify(this.domain);
diff --git a/gravitee-am-ui/src/app/domain/settings/providers/provider/settings/settings.component.html b/gravitee-am-ui/src/app/domain/settings/providers/provider/settings/settings.component.html
index fc18152559..e537295a15 100644
--- a/gravitee-am-ui/src/app/domain/settings/providers/provider/settings/settings.component.html
+++ b/gravitee-am-ui/src/app/domain/settings/providers/provider/settings/settings.component.html
@@ -57,15 +57,21 @@
       <h3>Manage identity provider</h3>
       <div class="gv-page-description-content">
         <p>
-          Register your users to interact with the access management server.
+          Register your users to interact with the Access Management {{organizationContext?'Portal':'Server'}}.
         </p>
       </div>
       <div *ngIf="provider.external" class="social-provider-settings-description">
         <h5>Social/Enterprise Identity Provider</h5>
         <small>1. Configure the redirect URI</small>
         <p class="social-provider-settings-description-contextual-help">The Redirect URI or callback URL or Assertion Consumer Service URL must be known by your Identity Server. Go to your Identity Server app settings page and specify the following URL</p>
-        <pre style="margin-bottom: 0;">http(s)://|AM-GATEWAY-HOST|/{{domain.path}}/login/callback</pre>
-        <pre style="margin-top: 0;">?provider={{provider.id}}</pre>
+        <div *ngIf="!organizationContext">
+          <pre style="margin-bottom: 0;">{{entrypoint.url}}/{{domain.path}}/login/callback</pre>
+          <pre style="margin-top: 0;">?provider={{provider.id}}</pre>
+        </div>
+        <div *ngIf="organizationContext">
+          <pre style="margin-bottom: 0;">{{entrypoint.url}}/auth/login/callback</pre>
+          <pre style="margin-top: 0;">?provider={{provider.id}}</pre>
+        </div>
         <small>2. (Optional) Custom login page</small>
         <p class="social-provider-settings-description-contextual-help">If you enabled custom login form and want to use the identity provider, update the login form content with the following code</p>
         <pre style="white-space: normal;">
diff --git a/gravitee-am-ui/src/app/domain/settings/providers/provider/settings/settings.component.ts b/gravitee-am-ui/src/app/domain/settings/providers/provider/settings/settings.component.ts
index ef8225f483..d309ede511 100644
--- a/gravitee-am-ui/src/app/domain/settings/providers/provider/settings/settings.component.ts
+++ b/gravitee-am-ui/src/app/domain/settings/providers/provider/settings/settings.component.ts
@@ -23,6 +23,7 @@ import { BreadcrumbService } from '../../../../../services/breadcrumb.service';
 import { DomainService } from '../../../../../services/domain.service';
 import { DialogService } from '../../../../../services/dialog.service';
 import * as _ from 'lodash';
+import {AppConfig} from "../../../../../../config/app.config";
 
 @Component({
   selector: 'provider-settings',
@@ -35,6 +36,7 @@ export class ProviderSettingsComponent implements OnInit {
   private certificates: any[];
   organizationContext = false;
   domain: any = {};
+  entrypoint: any = {};
   configurationIsValid: boolean = true;
   configurationPristine: boolean = true;
   providerSchema: any;
@@ -59,8 +61,10 @@ export class ProviderSettingsComponent implements OnInit {
     }
     if (this.organizationContext) {
       this.organizationService.settings().subscribe(data => this.domain = data);
+      this.entrypoint = { url: AppConfig.settings.baseURL};
     } else {
       this.domainService.get(this.domainId).subscribe(data => this.domain = data);
+      this.domainService.getEntrypoint(this.domainId).subscribe(data => this.entrypoint = data);
     }
     this.provider = this.route.snapshot.parent.data['provider'];
     this.providerConfiguration = JSON.parse(this.provider.configuration);
diff --git a/gravitee-am-ui/src/app/resolvers/domain-entrypoint.resolver.spec.ts b/gravitee-am-ui/src/app/resolvers/domain-entrypoint.resolver.spec.ts
new file mode 100644
index 0000000000..fc74c65d8f
--- /dev/null
+++ b/gravitee-am-ui/src/app/resolvers/domain-entrypoint.resolver.spec.ts
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {inject, TestBed} from '@angular/core/testing';
+
+describe('DomainEntrypointdResolver', () => {
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      providers: [DomainEntrypointResolver]
+    });
+  });
+
+  it('should ...', inject([DomainEntrypointResolver], (service: DomainEntrypointResolver) => {
+    expect(service).toBeTruthy();
+  }));
+});
diff --git a/gravitee-am-ui/src/app/resolvers/domain-entrypoint.resolver.ts b/gravitee-am-ui/src/app/resolvers/domain-entrypoint.resolver.ts
new file mode 100644
index 0000000000..ec8051e51b
--- /dev/null
+++ b/gravitee-am-ui/src/app/resolvers/domain-entrypoint.resolver.ts
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {Injectable} from '@angular/core';
+import {ActivatedRouteSnapshot, Resolve, RouterStateSnapshot} from '@angular/router';
+import {Observable} from 'rxjs';
+import {DomainService} from '../services/domain.service';
+
+@Injectable()
+export class DomainEntrypointResolver implements Resolve<any> {
+
+  constructor(private domainService: DomainService) {
+  }
+
+  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> | Promise<any> | any {
+    const domainId = (route.paramMap.get('domainId')) ? route.paramMap.get('domainId') :
+      route.parent.paramMap.get('domainId') ? route.parent.paramMap.get('domainId') :
+        route.parent.parent.paramMap.get('domainId') ? route.parent.parent.paramMap.get('domainId') :
+          route.parent.parent.parent.paramMap.get('domainId');
+    return this.domainService.getEntrypoint(domainId);
+  }
+
+}
diff --git a/gravitee-am-ui/src/app/resolvers/entrypoint.resolver.spec.ts b/gravitee-am-ui/src/app/resolvers/entrypoint.resolver.spec.ts
new file mode 100644
index 0000000000..6a25c3e987
--- /dev/null
+++ b/gravitee-am-ui/src/app/resolvers/entrypoint.resolver.spec.ts
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {inject, TestBed} from '@angular/core/testing';
+
+import {EntrypointResolver} from './entrypoint.resolver';
+
+describe('EntrypointResolver', () => {
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      providers: [EntrypointResolver]
+    });
+  });
+
+  it('should ...', inject([EntrypointResolver], (service: EntrypointResolver) => {
+    expect(service).toBeTruthy();
+  }));
+});
diff --git a/gravitee-am-ui/src/app/resolvers/entrypoint.resolver.ts b/gravitee-am-ui/src/app/resolvers/entrypoint.resolver.ts
new file mode 100644
index 0000000000..88b8975a19
--- /dev/null
+++ b/gravitee-am-ui/src/app/resolvers/entrypoint.resolver.ts
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {Injectable} from '@angular/core';
+import {ActivatedRouteSnapshot, Resolve, RouterStateSnapshot} from "@angular/router";
+import {Observable} from "rxjs";
+import {EntrypointService} from "../services/entrypoint.service";
+
+@Injectable()
+export class EntrypointResolver implements Resolve<any> {
+
+  constructor(private entrypointService: EntrypointService) { }
+
+  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any>|Promise<any>|any {
+    let entrypointId: string = route.paramMap.get('entrypointId');
+    return this.entrypointService.get(entrypointId);
+  }
+
+}
diff --git a/gravitee-am-ui/src/app/resolvers/entrypoints.resolver.spec.ts b/gravitee-am-ui/src/app/resolvers/entrypoints.resolver.spec.ts
new file mode 100644
index 0000000000..2c0878b2df
--- /dev/null
+++ b/gravitee-am-ui/src/app/resolvers/entrypoints.resolver.spec.ts
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {inject, TestBed} from '@angular/core/testing';
+import {EntrypointsResolver} from "./entrypoints.resolver";
+
+describe('EntrypointsResolver', () => {
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      providers: [EntrypointsResolver]
+    });
+  });
+
+  it('should ...', inject([EntrypointsResolver], (service: EntrypointsResolver) => {
+    expect(service).toBeTruthy();
+  }));
+});
diff --git a/gravitee-am-ui/src/app/resolvers/entrypoints.resolver.ts b/gravitee-am-ui/src/app/resolvers/entrypoints.resolver.ts
new file mode 100644
index 0000000000..4992f9ad3f
--- /dev/null
+++ b/gravitee-am-ui/src/app/resolvers/entrypoints.resolver.ts
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {Injectable} from '@angular/core';
+import {ActivatedRouteSnapshot, Resolve, RouterStateSnapshot} from "@angular/router";
+import {Observable} from "rxjs";
+import {EntrypointService} from "../services/entrypoint.service";
+
+@Injectable()
+export class EntrypointsResolver implements Resolve<any> {
+
+  constructor(private entrypointService: EntrypointService) { }
+
+  resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any>|Promise<any>|any {
+    return this.entrypointService.list();
+  }
+}
diff --git a/gravitee-am-ui/src/app/services/auth.service.ts b/gravitee-am-ui/src/app/services/auth.service.ts
index 75b1a23e62..40a0156213 100644
--- a/gravitee-am-ui/src/app/services/auth.service.ts
+++ b/gravitee-am-ui/src/app/services/auth.service.ts
@@ -39,7 +39,7 @@ export class AuthService {
 
   handleAuthentication(): Observable<boolean> {
     // authentication success
-    return Observable.create(observer => observer.next(true));
+    return new Observable(observer => observer.next(true));
   }
 
   userInfo(): Observable<any> {
@@ -54,7 +54,7 @@ export class AuthService {
   }
 
   logout(): Observable<boolean> {
-    return Observable.create(observer => {
+    return new Observable(observer => {
       observer.next(true);
     });
   }
diff --git a/gravitee-am-ui/src/app/services/domain.service.ts b/gravitee-am-ui/src/app/services/domain.service.ts
index c905541bea..88dba00a91 100644
--- a/gravitee-am-ui/src/app/services/domain.service.ts
+++ b/gravitee-am-ui/src/app/services/domain.service.ts
@@ -36,6 +36,26 @@ export class DomainService {
     return this.http.get<any>(this.domainsURL + id);
   }
 
+  getEntrypoints(id: string): Observable<any> {
+    return this.http.get<any>(this.domainsURL + id + '/entrypoints');
+  }
+
+  getEntrypoint(id: string): Observable<any> {
+    return this.getEntrypoints(id)
+      .pipe(map(entrypoints => {
+        let entrypoint;
+
+        if (entrypoints.length == 1) {
+          entrypoint = entrypoints[0];
+        } else {
+          entrypoint = entrypoints.filter(e => !e.defaultEntrypoint)[0];
+        }
+
+        return entrypoint;
+      }));
+  }
+
+
   create(domain): Observable<any> {
     return this.http.post<any>(this.domainsURL, domain);
   }
diff --git a/gravitee-am-ui/src/app/services/entrypoint.service.spec.ts b/gravitee-am-ui/src/app/services/entrypoint.service.spec.ts
new file mode 100644
index 0000000000..3b895a3722
--- /dev/null
+++ b/gravitee-am-ui/src/app/services/entrypoint.service.spec.ts
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { TestBed, inject } from '@angular/core/testing';
+
+import { EntrypointService } from './tag.service';
+
+describe('EntrypointService', () => {
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      providers: [EntrypointService]
+    });
+  });
+
+  it('should be created', inject([EntrypointService], (service: EntrypointService) => {
+    expect(service).toBeTruthy();
+  }));
+});
diff --git a/gravitee-am-ui/src/app/services/entrypoint.service.ts b/gravitee-am-ui/src/app/services/entrypoint.service.ts
new file mode 100644
index 0000000000..b684fb1738
--- /dev/null
+++ b/gravitee-am-ui/src/app/services/entrypoint.service.ts
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {Injectable} from '@angular/core';
+import {AppConfig} from "../../config/app.config";
+import {HttpClient} from "@angular/common/http";
+import {Observable} from "rxjs";
+
+@Injectable()
+export class EntrypointService {
+  private entrypointsURL = AppConfig.settings.organizationBaseURL + '/entrypoints/';
+
+  constructor(private http: HttpClient) { }
+
+  list(): Observable<any> {
+    return this.http.get<any>(this.entrypointsURL);
+  }
+
+  get(id): Observable<any> {
+    return this.http.get<any>(this.entrypointsURL + id);
+  }
+
+  create(entrypoint): Observable<any> {
+    return this.http.post<any>(this.entrypointsURL, entrypoint);
+  }
+
+  update(id, entrypoint): Observable<any> {
+    return this.http.put<any>(this.entrypointsURL + id, {
+      'name' : entrypoint.name,
+      'description' : entrypoint.description,
+      'url': entrypoint.url,
+      'tags': entrypoint.tags
+    });
+  }
+
+  delete(id): Observable<any> {
+    return this.http.delete<any>(this.entrypointsURL + id);
+  }
+}
diff --git a/gravitee-am-ui/src/app/settings/management/entrypoints/creation/entrypoint-creation.component.html b/gravitee-am-ui/src/app/settings/management/entrypoints/creation/entrypoint-creation.component.html
new file mode 100644
index 0000000000..34ccb5dc2c
--- /dev/null
+++ b/gravitee-am-ui/src/app/settings/management/entrypoints/creation/entrypoint-creation.component.html
@@ -0,0 +1,56 @@
+<!--
+
+    Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+            http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<div class="gv-page-container">
+  <a [routerLink]="['..']" class="gv-back-link"><small><< Back to entrypoints</small></a>
+  <h1>New Entrypoint</h1>
+  <div>
+    <form (ngSubmit)="create()" #entrypointForm="ngForm" fxLayout="column" fxFlex="70">
+      <div class="gv-form-section">
+        <mat-form-field appearance="outline" floatLabel="always">
+          <mat-label>Name</mat-label>
+          <input matInput type="text" placeholder="Name" name="name" [(ngModel)]="entrypoint.name" required>
+          <mat-hint>A name for the entrypoint.</mat-hint>
+        </mat-form-field>
+
+        <mat-form-field appearance="outline" floatLabel="always">
+          <mat-label>Description</mat-label>
+          <input matInput type="text" placeholder="Description" name="description" [(ngModel)]="entrypoint.description">
+          <mat-hint>A description for the entrypoint.</mat-hint>
+        </mat-form-field>
+
+        <mat-form-field appearance="outline" floatLabel="always">
+          <mat-label>Entrypoint url</mat-label>
+          <input matInput type="text" placeholder="Entrypoint" name="url" [(ngModel)]="entrypoint.url" required pattern="https?://.+">
+          <mat-hint>An url for the entrypoint.</mat-hint>
+        </mat-form-field>
+      </div>
+      <div fxLayout="row" fxLayoutAlign="end">
+        <a mat-button [routerLink]="['..']" style="margin-right: 20px;">CANCEL</a>
+        <button mat-raised-button [disabled]="!entrypointForm.valid || entrypointForm.pristine" type="submit">CREATE</button>
+      </div>
+    </form>
+    <div class="gv-page-description" fxFlex>
+      <h3>Entrypoint ?</h3>
+      <div class="gv-page-description-content">
+        <p>
+          An entrypoint allows you to display the url to use when end-user applications will contact the AM Gateway.
+        </p>
+      </div>
+    </div>
+  </div>
+</div>
diff --git a/gravitee-am-ui/src/app/settings/management/entrypoints/creation/entrypoint-creation.component.scss b/gravitee-am-ui/src/app/settings/management/entrypoints/creation/entrypoint-creation.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/gravitee-am-ui/src/app/settings/management/entrypoints/creation/entrypoint-creation.component.spec.ts b/gravitee-am-ui/src/app/settings/management/entrypoints/creation/entrypoint-creation.component.spec.ts
new file mode 100644
index 0000000000..65a69ed9ea
--- /dev/null
+++ b/gravitee-am-ui/src/app/settings/management/entrypoints/creation/entrypoint-creation.component.spec.ts
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {async, ComponentFixture, TestBed} from '@angular/core/testing';
+
+import {EntrypointCreationComponent} from './entrypoint-creation.component';
+
+describe('EntrypointCreationComponent', () => {
+  let component: EntrypointCreationComponent;
+  let fixture: ComponentFixture<EntrypointCreationComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ EntrypointCreationComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(EntrypointCreationComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/gravitee-am-ui/src/app/settings/management/entrypoints/creation/entrypoint-creation.component.ts b/gravitee-am-ui/src/app/settings/management/entrypoints/creation/entrypoint-creation.component.ts
new file mode 100644
index 0000000000..19f2a7598d
--- /dev/null
+++ b/gravitee-am-ui/src/app/settings/management/entrypoints/creation/entrypoint-creation.component.ts
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {Component} from '@angular/core';
+import {ActivatedRoute, Router} from "@angular/router";
+import {SnackbarService} from "../../../../services/snackbar.service";
+import {EntrypointService} from "../../../../services/entrypoint.service";
+
+@Component({
+  selector: 'app-creation',
+  templateUrl: './entrypoint-creation.component.html',
+  styleUrls: ['./entrypoint-creation.component.scss']
+})
+export class EntrypointCreationComponent {
+  entrypoint: any = { tags: [] };
+
+  constructor(private entrypointService: EntrypointService, private router: Router, private route: ActivatedRoute,
+              private snackbarService : SnackbarService) { }
+
+  create() {
+    this.entrypointService.create(this.entrypoint).subscribe(data => {
+      this.snackbarService.open("Entrypoint " + data.name + " created");
+      this.router.navigate(['/settings', 'management', 'entrypoints', data.id]);
+    });
+  }
+
+}
diff --git a/gravitee-am-ui/src/app/settings/management/entrypoints/entrypoint/entrypoint.component.html b/gravitee-am-ui/src/app/settings/management/entrypoints/entrypoint/entrypoint.component.html
new file mode 100644
index 0000000000..22f4888e79
--- /dev/null
+++ b/gravitee-am-ui/src/app/settings/management/entrypoints/entrypoint/entrypoint.component.html
@@ -0,0 +1,102 @@
+<!--
+
+    Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+            http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<div class="gv-page-container">
+  <a [routerLink]="['..']" class="gv-back-link"><small><< Back to entrypoints</small></a>
+  <h1>{{entrypoint.name}}</h1>
+  <div fxLayout="row">
+    <div fxLayout="column" fxFlex="70">
+      <form (keydown.enter)="false" (ngSubmit)="update()" #entrypointForm="ngForm" fxLayout="column">
+        <div class="gv-form-section">
+          <mat-form-field appearance="outline" floatLabel="always">
+            <mat-label>Name</mat-label>
+            <input matInput type="text" placeholder="Name" name="name" [(ngModel)]="entrypoint.name" required [readonly]="readonly" [disabled]="entrypoint.defaultEntrypoint">
+            <mat-hint>Entrypoint's name</mat-hint>
+          </mat-form-field>
+
+          <mat-form-field appearance="outline" floatLabel="always">
+            <mat-label>Description</mat-label>
+            <input matInput type="text" placeholder="Description" name="description" [(ngModel)]="entrypoint.description" [readonly]="readonly" [disabled]="entrypoint.defaultEntrypoint">
+            <mat-hint>Entrypoint's description</mat-hint>
+          </mat-form-field>
+
+          <mat-form-field appearance="outline" floatLabel="always">
+            <mat-label>Entrypoint url</mat-label>
+            <input matInput type="text" placeholder="url" name="url" [(ngModel)]="entrypoint.url" [readonly]="readonly" required pattern="https?://.+">
+            <mat-hint>Entrypoint's url</mat-hint>
+          </mat-form-field>
+
+          <mat-form-field appearance="outline" floatLabel="always">
+            <mat-label>Sharding tags</mat-label>
+            <mat-chip-list #chipListTags>
+              <mat-chip *ngFor="let tag of selectedTags" color="accent"
+                        selected="true" (removed)="removeTag(tag)">
+                <mat-icon style="vertical-align:middle;">filter_none</mat-icon>
+                &nbsp;<span>{{tag.id}}</span>&nbsp;|&nbsp;<small>{{tag.name}}</small>
+                <mat-icon matChipRemove>cancel</mat-icon>
+              </mat-chip>
+              <input matInput
+                     aria-label="Sharding Tags"
+                     placeholder="Sharding Tags"
+                     [matAutocomplete]="auto"
+                     [matChipInputFor]="chipListTags" [readonly]="readonly" [disabled]="entrypoint.defaultEntrypoint"
+                     #chipInput>
+            </mat-chip-list>
+
+            <mat-autocomplete #auto="matAutocomplete" (optionSelected)="addTag($event)">
+              <mat-option *ngFor="let tag of tags" [value]="tag.id">
+                <mat-icon style="vertical-align:middle;">filter_none</mat-icon>
+                <span>{{ tag.id }}</span> | <small>{{tag.name}}</small>
+              </mat-option>
+            </mat-autocomplete>
+            <mat-hint>Select a sharding tag and press enter.</mat-hint>
+          </mat-form-field>
+        </div>
+
+        <div fxLayout="row" *hasPermission="['organization_entrypoint_update']">
+          <button mat-raised-button [disabled]="(!entrypointForm.valid || entrypointForm.pristine) && !formChanged" type="submit">SAVE</button>
+        </div>
+
+      </form>
+
+      <div class="gv-page-delete-zone" fxLayout="column" *ngIf="canDelete()">
+        <p>This operation cannot be undone.</p>
+        Entrypoint will be deleted and will not be used for gateway deployment.
+        <div class="gv-page-delete-button" fxLayout="row">
+          <span fxFlex></span>
+          <button mat-raised-button color="warn" (click)="delete($event)">DELETE</button>
+        </div>
+      </div>
+    </div>
+    <div class="gv-page-description" fxFlex>
+      <h3>Manage entrypoint</h3>
+      <div class="gv-page-description-content">
+        <p>
+          An entrypoint determines which url your end-users will have to use to contact the AM Gateway.
+        </p>
+        <p>
+          Entrypoints are associated with tags. When a tag is also associated to a domain, the corresponding entrypoint will be automatically selected to display the right url to use to contact the Gateway.
+        </p>
+        <p>
+          So, if you associate one of the following tags [ <small style="color:grey;">{{entrypoint.tags.length > 0 ? entrypoint.tags.join(', ') : 'No tag defined'}}</small> ] to a domain <small style="color:grey;">my-domain</small>,
+          domain users will access to AM Gateway through the following url <small style="color:grey;">{{entrypoint.url}}/my-domain</small>
+        </p>
+      </div>
+    </div>
+  </div>
+</div>
+
diff --git a/gravitee-am-ui/src/app/settings/management/entrypoints/entrypoint/entrypoint.component.scss b/gravitee-am-ui/src/app/settings/management/entrypoints/entrypoint/entrypoint.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/gravitee-am-ui/src/app/settings/management/entrypoints/entrypoint/entrypoint.component.spec.ts b/gravitee-am-ui/src/app/settings/management/entrypoints/entrypoint/entrypoint.component.spec.ts
new file mode 100644
index 0000000000..4d85ab248a
--- /dev/null
+++ b/gravitee-am-ui/src/app/settings/management/entrypoints/entrypoint/entrypoint.component.spec.ts
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {async, ComponentFixture, TestBed} from '@angular/core/testing';
+
+import {EntrypointComponent} from './entrypoint.component';
+
+describe('RoleComponent', () => {
+  let component: EntrypointComponent;
+  let fixture: ComponentFixture<EntrypointComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ EntrypointComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(EntrypointComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/gravitee-am-ui/src/app/settings/management/entrypoints/entrypoint/entrypoint.component.ts b/gravitee-am-ui/src/app/settings/management/entrypoints/entrypoint/entrypoint.component.ts
new file mode 100644
index 0000000000..1c84282a5c
--- /dev/null
+++ b/gravitee-am-ui/src/app/settings/management/entrypoints/entrypoint/entrypoint.component.ts
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {Component, OnInit, ViewChild} from '@angular/core';
+import {NgForm} from '@angular/forms';
+import {ActivatedRoute, Router} from '@angular/router';
+import {SnackbarService} from '../../../../services/snackbar.service';
+import {BreadcrumbService} from '../../../../services/breadcrumb.service';
+import {EntrypointService} from '../../../../services/entrypoint.service';
+import {DialogService} from '../../../../services/dialog.service';
+import {AuthService} from '../../../../services/auth.service';
+import {Tag} from "../../../../domain/settings/general/general.component";
+import * as _ from 'lodash';
+import {MatInput} from "@angular/material/input";
+
+@Component({
+  selector: 'app-entrypoint',
+  templateUrl: './entrypoint.component.html',
+  styleUrls: ['./entrypoint.component.scss']
+})
+export class EntrypointComponent implements OnInit {
+  entrypoint: any;
+  @ViewChild('entrypointForm') public entrypointForm: NgForm;
+  @ViewChild('chipInput') chipInput: MatInput;
+  formChanged = false;
+  readonly: boolean;
+  tags: Tag[];
+  selectedTags: Tag[];
+
+  constructor(private entrypointService: EntrypointService,
+              private snackbarService: SnackbarService,
+              private route: ActivatedRoute,
+              private router: Router,
+              private breadcrumbService: BreadcrumbService,
+              private dialogService: DialogService,
+              private authService: AuthService) { }
+
+  ngOnInit() {
+    this.entrypoint = this.route.snapshot.data['entrypoint'];
+    this.readonly = !this.authService.hasPermissions(['organization_entrypoint_update']);
+    this.initBreadcrumb();
+    this.initTags();
+  }
+
+  initTags() {
+    this.tags = this.route.snapshot.data['tags'];
+    this.selectedTags = this.entrypoint.tags.map(t => _.find(this.tags, { 'id': t })).filter(t => typeof t !== 'undefined');
+    this.tags = _.difference(this.tags, this.selectedTags);
+  }
+
+  addTag(event) {
+    this.selectedTags = this.selectedTags.concat(_.remove(this.tags, { 'id': event.option.value }));
+    this.tagsChanged();
+  }
+
+  removeTag(tag) {
+    this.selectedTags = this.selectedTags.filter(t => t.id !== tag.id);
+    this.tags.push(tag);
+    this.tagsChanged();
+  }
+
+  tagsChanged() {
+    this.chipInput['nativeElement'].blur();
+    this.formChanged = true;
+    this.entrypoint.tags = _.map(this.selectedTags, tag => tag.id);
+  }
+
+  update() {
+    this.entrypointService.update(this.entrypoint.id, this.entrypoint).subscribe(data => {
+      this.entrypoint = data;
+      this.initBreadcrumb();
+      this.entrypointForm.reset(Object.assign({}, this.entrypoint));
+      this.snackbarService.open('Entrypoint updated');
+    });
+  }
+
+  delete(event) {
+    event.preventDefault();
+    this.dialogService
+      .confirm('Delete Entrypoint', 'Are you sure you want to delete this entrypoint ?')
+      .subscribe(res => {
+        if (res) {
+          this.entrypointService.delete(this.entrypoint.id).subscribe(response => {
+            this.snackbarService.open('Entrypoint deleted');
+            this.router.navigate(['/settings', 'management', 'entrypoints']);
+          });
+        }
+      });
+  }
+
+  initBreadcrumb() {
+    this.breadcrumbService.addFriendlyNameForRouteRegex('/settings/management/entrypoints/' + this.entrypoint.id + '$', this.entrypoint.name);
+  }
+
+  canDelete() {
+    return !this.entrypoint.defaultEntrypoint && this.authService.hasPermissions(['organization_entrypoint_delete'])
+  }
+}
diff --git a/gravitee-am-ui/src/app/settings/management/entrypoints/entrypoints.component.html b/gravitee-am-ui/src/app/settings/management/entrypoints/entrypoints.component.html
new file mode 100644
index 0000000000..0d05fc7e89
--- /dev/null
+++ b/gravitee-am-ui/src/app/settings/management/entrypoints/entrypoints.component.html
@@ -0,0 +1,63 @@
+<!--
+
+    Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+            http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<div class="gv-page-container" fxLayout="column">
+  <h1 *ngIf="!isEmpty">Entrypoints</h1>
+
+  <div *ngIf="!isEmpty">
+    <ngx-datatable class="material"
+                   [columnMode]="'flex'"
+                   [headerHeight]="40"
+                   [footerHeight]="40"
+                   [rowHeight]="55"
+                   [rows]='entrypoints'
+                   [sorts]="[{prop: 'name', dir: 'asc'}]">
+      <ngx-datatable-column name="Name" [flexGrow]="1">
+        <ng-template let-row="row" ngx-datatable-cell-template>
+         <div class="entrypoint-name" fxLayout="column">
+            <h3>{{row.name}}</h3>
+            <small>{{row.description}}</small>
+          </div>
+        </ng-template>
+      </ngx-datatable-column>
+      <ngx-datatable-column name="Endpoint" [flexGrow]="1">
+        <ng-template let-row="row" ngx-datatable-cell-template>
+          {{row.url}}
+        </ng-template>
+      </ngx-datatable-column>
+      <ngx-datatable-column name="Actions" [flexGrow]="1">
+        <ng-template let-row="row" ngx-datatable-cell-template>
+          <div fxLayout="row" class="gv-table-cell-actions">
+            <a mat-icon-button [routerLink]="[row.id]" *hasPermission="['organization_entrypoint_read']"><mat-icon>settings</mat-icon></a>
+            <button *ngIf="canDelete(row)" mat-icon-button (click)="delete(row.id, $event)"><mat-icon>delete</mat-icon></button>
+          </div>
+        </ng-template>
+      </ngx-datatable-column>
+    </ngx-datatable>
+  </div>
+
+  <app-emptystate *ngIf="isEmpty"
+                  [message]="'Entrypoints will appear here'"
+                  [subMessage]="'Start creating a entrypoint'"
+                  [icon]="'filter_none'"></app-emptystate>
+
+  <div *hasPermission="['organization_entrypoint_create']" [ngClass]="{'gv-add-button': !isEmpty, 'gv-add-button-center': isEmpty}">
+    <a mat-fab color="accent" [routerLink]="['new']">
+      <mat-icon>add</mat-icon>
+    </a>
+  </div>
+</div>
diff --git a/gravitee-am-ui/src/app/settings/management/entrypoints/entrypoints.component.scss b/gravitee-am-ui/src/app/settings/management/entrypoints/entrypoints.component.scss
new file mode 100644
index 0000000000..c9192eaefc
--- /dev/null
+++ b/gravitee-am-ui/src/app/settings/management/entrypoints/entrypoints.component.scss
@@ -0,0 +1,14 @@
+.gv-page-container {
+  ngx-datatable {
+    .entrypoint-name {
+      h3 {
+        font-size: 14px;
+        font-weight: normal;
+        margin: 0;
+      }
+      small {
+        color: rgba(0, 0, 0, .54);
+      }
+    }
+  }
+}
diff --git a/gravitee-am-ui/src/app/settings/management/entrypoints/entrypoints.component.spec.ts b/gravitee-am-ui/src/app/settings/management/entrypoints/entrypoints.component.spec.ts
new file mode 100644
index 0000000000..d56ad5ef00
--- /dev/null
+++ b/gravitee-am-ui/src/app/settings/management/entrypoints/entrypoints.component.spec.ts
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {async, ComponentFixture, TestBed} from '@angular/core/testing';
+
+import {EntrypointsComponent} from './entrypoints.component';
+
+describe('EntypointsComponent', () => {
+  let component: EntrypointsComponent;
+  let fixture: ComponentFixture<EntrypointsComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ EntrypointsComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(EntrypointsComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should be created', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/gravitee-am-ui/src/app/settings/management/entrypoints/entrypoints.component.ts b/gravitee-am-ui/src/app/settings/management/entrypoints/entrypoints.component.ts
new file mode 100644
index 0000000000..ad346a3030
--- /dev/null
+++ b/gravitee-am-ui/src/app/settings/management/entrypoints/entrypoints.component.ts
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {Component, OnInit} from '@angular/core';
+import {EntrypointService} from "../../../services/entrypoint.service";
+import {SnackbarService} from "../../../services/snackbar.service";
+import {DialogService} from "../../../services/dialog.service";
+import {ActivatedRoute, Router} from "@angular/router";
+import {AuthService} from "../../../services/auth.service";
+
+@Component({
+  selector: 'app-entrypoints',
+  templateUrl: './entrypoints.component.html',
+  styleUrls: ['./entrypoints.component.scss']
+})
+export class EntrypointsComponent implements OnInit {
+  private entrypoints: any[];
+
+  constructor(private entrypointService: EntrypointService,
+              private dialogService: DialogService,
+              private snackbarService: SnackbarService,
+              private route: ActivatedRoute,
+              private authService: AuthService) {
+  }
+
+  ngOnInit() {
+    this.entrypoints = this.route.snapshot.data['entrypoints'];
+  }
+
+  get isEmpty() {
+    return !this.entrypoints || this.entrypoints.length == 0
+  }
+
+  loadEntrypoints() {
+    this.entrypointService.list().subscribe(response => this.entrypoints = response);
+  }
+
+  delete(id, event) {
+    event.preventDefault();
+    this.dialogService
+      .confirm('Delete Entrypoint', 'Are you sure you want to delete this entrypoint ?')
+      .subscribe(res => {
+        if (res) {
+          this.entrypointService.delete(id).subscribe(response => {
+            this.snackbarService.open("Entrypoint deleted");
+            this.loadEntrypoints();
+          });
+        }
+      });
+  }
+
+  canDelete(entrypoint) {
+    return !entrypoint.defaultEntrypoint && this.authService.hasPermissions(['organization_entrypoint_delete'])
+  }
+}
diff --git a/gravitee-am-ui/src/app/settings/management/tags/tags.component.html b/gravitee-am-ui/src/app/settings/management/tags/tags.component.html
index 261dcae9c6..e2c69926cc 100644
--- a/gravitee-am-ui/src/app/settings/management/tags/tags.component.html
+++ b/gravitee-am-ui/src/app/settings/management/tags/tags.component.html
@@ -39,7 +39,7 @@
       <ngx-datatable-column name="Actions" [flexGrow]="1">
         <ng-template let-row="row" ngx-datatable-cell-template>
           <div fxLayout="row" class="gv-table-cell-actions">
-            <a mat-icon-button [routerLink]="[row.id]"><mat-icon>settings</mat-icon></a>
+            <a mat-icon-button [routerLink]="[row.id]" *hasPermission="['organization_tag_read']"><mat-icon>settings</mat-icon></a>
             <button *hasPermission="['organization_tag_delete']" mat-icon-button (click)="delete(row.id, $event)"><mat-icon>delete</mat-icon></button>
           </div>
         </ng-template>
diff --git a/gravitee-am-ui/src/app/settings/management/tags/tags.component.spec.ts b/gravitee-am-ui/src/app/settings/management/tags/tags.component.spec.ts
index 15db2aaad1..94fce6d245 100644
--- a/gravitee-am-ui/src/app/settings/management/tags/tags.component.spec.ts
+++ b/gravitee-am-ui/src/app/settings/management/tags/tags.component.spec.ts
@@ -15,9 +15,9 @@
  */
 import { async, ComponentFixture, TestBed } from '@angular/core/testing';
 
-import { TagsComponent } from './users.component';
+import { TagsComponent } from './tags.component';
 
-describe('UsersComponent', () => {
+describe('TagsComponent', () => {
   let component: TagsComponent;
   let fixture: ComponentFixture<TagsComponent>;
 
diff --git a/gravitee-am-ui/src/styles.scss b/gravitee-am-ui/src/styles.scss
index 3860d9747c..fa2ea5adfd 100644
--- a/gravitee-am-ui/src/styles.scss
+++ b/gravitee-am-ui/src/styles.scss
@@ -267,3 +267,30 @@ p.gv-plugin-creation-description {
   background-color: #039be5;
   color: #fafafa;
 }
+
+div.code {
+  overflow: auto;
+  white-space: pre;
+  background: #f5f5f5!important;
+  border-color: transparent!important;
+  margin-bottom: 24px;
+  padding: 0px 20px;
+  font-size: 1em;
+
+  button {
+    width: auto;
+    float: right;
+    margin-left: 20px;
+    background: #f5f5f5!important;
+  }
+
+  pre {
+    width: auto;
+    overflow: auto;
+    white-space: pre-wrap;
+
+    &.multiline {
+      white-space: unset;
+    }
+  }
+}


Test patch:
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/JerseySpringTest.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/JerseySpringTest.java
index 686dd1ba7f..48602566f7 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/JerseySpringTest.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/JerseySpringTest.java
@@ -15,7 +15,7 @@
  */
 package io.gravitee.am.management.handlers.management.api;
 
-import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import io.gravitee.am.identityprovider.api.DefaultUser;
 import io.gravitee.am.identityprovider.api.User;
@@ -49,9 +49,13 @@ import javax.ws.rs.client.Entity;
 import javax.ws.rs.client.WebTarget;
 import javax.ws.rs.container.ContainerRequestContext;
 import javax.ws.rs.container.ContainerRequestFilter;
-import javax.ws.rs.core.*;
+import javax.ws.rs.core.Application;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.SecurityContext;
 import java.io.IOException;
 import java.security.Principal;
+import java.util.List;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
@@ -159,6 +163,9 @@ public abstract class JerseySpringTest {
     @Autowired
     protected MembershipService membershipService;
 
+    @Autowired
+    protected EntrypointService entrypointService;
+
     @Before
     public void init() {
         when(permissionService.hasPermission(any(User.class), any(PermissionAcls.class))).thenReturn(Single.just(true));
@@ -327,6 +334,11 @@ public abstract class JerseySpringTest {
         public MembershipService membershipService() {
             return mock(MembershipService.class);
         }
+
+        @Bean
+        public EntrypointService entrypointService() {
+            return mock(EntrypointService.class);
+        }
     }
 
     private JerseyTest _jerseyTest;
@@ -409,12 +421,22 @@ public abstract class JerseySpringTest {
             if (clazz == String.class) {
                 return (T) response.readEntity(String.class);
             }
+
             return objectMapper.readValue(response.readEntity(String.class), clazz);
         } catch (IOException e) {
             throw new RuntimeException(e);
         }
     }
 
+    protected <T> List<T> readListEntity(Response response, Class<T> entityClazz) {
+
+        try {
+            return objectMapper.readValue(response.readEntity(String.class), objectMapper.getTypeFactory().constructCollectionType(List.class, entityClazz));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     /**
      * Allows to put entity using object mapper instead of jersey's own implementation.
      * It is especially useful to ensure objects are serialized to json using same feature (ex: lower cased enum, ...).
@@ -433,4 +455,13 @@ public abstract class JerseySpringTest {
             throw new RuntimeException(e);
         }
     }
+
+    protected <T> Response post(WebTarget webTarget, T value) {
+
+        try {
+            return webTarget.request().post(Entity.entity(objectMapper.writeValueAsString(value), MediaType.APPLICATION_JSON_TYPE));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
 }
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/resources/DomainResourceTest.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/resources/DomainResourceTest.java
index c655c186a5..32fa0bad0c 100644
--- a/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/resources/DomainResourceTest.java
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/resources/DomainResourceTest.java
@@ -20,6 +20,7 @@ import io.gravitee.am.management.handlers.management.api.JerseySpringTest;
 import io.gravitee.am.management.service.permissions.PermissionAcls;
 import io.gravitee.am.model.Acl;
 import io.gravitee.am.model.Domain;
+import io.gravitee.am.model.Entrypoint;
 import io.gravitee.am.model.ReferenceType;
 import io.gravitee.am.model.account.AccountSettings;
 import io.gravitee.am.model.login.LoginSettings;
@@ -29,16 +30,14 @@ import io.gravitee.am.model.scim.SCIMSettings;
 import io.gravitee.am.service.exception.TechnicalManagementException;
 import io.gravitee.am.service.model.PatchDomain;
 import io.gravitee.common.http.HttpStatusCode;
+import io.reactivex.Flowable;
 import io.reactivex.Maybe;
 import io.reactivex.Single;
 import org.junit.Test;
 
-import javax.ws.rs.client.Entity;
 import javax.ws.rs.core.Response;
 
-import java.util.Collections;
-import java.util.Date;
-import java.util.Optional;
+import java.util.*;
 
 import static org.junit.Assert.*;
 import static org.mockito.ArgumentMatchers.*;
@@ -50,6 +49,16 @@ import static org.mockito.Mockito.doReturn;
  */
 public class DomainResourceTest extends JerseySpringTest {
 
+
+    public static final String ORGANIZATION_ID = "orga-1";
+    public static final String ENTRYPOINT_ID1 = "entrypoint-1";
+    public static final String ENVIRONMENT_ID = "env-1";
+    public static final String DOMAIN_ID = "domain-1";
+    public static final String TAG_ID2 = "tag#2";
+    public static final String TAG_ID1 = "tag#1";
+    public static final String ENTRYPOINT_ID2 = "entrypoint-2";
+    public static final String ENTRYPOINT_ID_DEFAULT = "DEFAULT";
+
     @Test
     public void shouldGetDomain() {
 
@@ -223,6 +232,139 @@ public class DomainResourceTest extends JerseySpringTest {
         assertEquals(HttpStatusCode.BAD_REQUEST_400, response.getStatus());
     }
 
+    @Test
+    public void shouldGetEntrypoints_entrypoint1() {
+
+        final Entrypoint entrypoint = new Entrypoint();
+        entrypoint.setId(ENTRYPOINT_ID1);
+        entrypoint.setName("entrypoint-1-name");
+        entrypoint.setTags(Arrays.asList(TAG_ID2));
+        entrypoint.setOrganizationId(ORGANIZATION_ID);
+
+        final Entrypoint entrypoint2 = new Entrypoint();
+        entrypoint2.setId(ENTRYPOINT_ID2);
+        entrypoint2.setName("entrypoint-2-name");
+        entrypoint2.setTags(Collections.emptyList());
+        entrypoint2.setOrganizationId(ORGANIZATION_ID);
+
+        final Entrypoint defaultEntrypoint = new Entrypoint();
+        defaultEntrypoint.setId(ENTRYPOINT_ID_DEFAULT);
+        defaultEntrypoint.setName("Default");
+        defaultEntrypoint.setTags(Collections.emptyList());
+        defaultEntrypoint.setDefaultEntrypoint(true);
+        defaultEntrypoint.setOrganizationId(ORGANIZATION_ID);
+
+        Domain mockDomain = new Domain();
+        mockDomain.setId(DOMAIN_ID);
+        mockDomain.setTags(new HashSet<>(Arrays.asList(TAG_ID1, TAG_ID2)));
+
+        doReturn(Maybe.just(mockDomain)).when(domainService).findById(mockDomain.getId());
+        doReturn(Flowable.just(entrypoint, entrypoint2, defaultEntrypoint)).when(entrypointService).findAll(ORGANIZATION_ID);
+
+        final Response response = target("organizations")
+                .path(ORGANIZATION_ID)
+                .path("environments")
+                .path(ENVIRONMENT_ID)
+                .path("domains")
+                .path(DOMAIN_ID)
+                .path("entrypoints").request().get();
+
+        assertEquals(HttpStatusCode.OK_200, response.getStatus());
+        List<Entrypoint> entrypoints = readListEntity(response, Entrypoint.class);
+
+        assertEquals(1, entrypoints.size());
+        assertTrue(entrypoints.stream().anyMatch(e -> e.getId().equals(ENTRYPOINT_ID1)));
+    }
+
+    @Test
+    public void shouldGetEntrypoints_default() {
+
+        final Entrypoint entrypoint = new Entrypoint();
+        entrypoint.setId(ENTRYPOINT_ID1);
+        entrypoint.setName("entrypoint-1-name");
+        entrypoint.setTags(Collections.emptyList());
+        entrypoint.setOrganizationId(ORGANIZATION_ID);
+
+        final Entrypoint entrypoint2 = new Entrypoint();
+        entrypoint2.setId(ENTRYPOINT_ID2);
+        entrypoint2.setName("entrypoint-2-name");
+        entrypoint2.setTags(Collections.emptyList());
+        entrypoint2.setOrganizationId(ORGANIZATION_ID);
+
+        final Entrypoint defaultEntrypoint = new Entrypoint();
+        defaultEntrypoint.setId(ENTRYPOINT_ID_DEFAULT);
+        defaultEntrypoint.setName("Default");
+        defaultEntrypoint.setTags(Collections.emptyList());
+        defaultEntrypoint.setDefaultEntrypoint(true);
+        defaultEntrypoint.setOrganizationId(ORGANIZATION_ID);
+
+        Domain mockDomain = new Domain();
+        mockDomain.setId(DOMAIN_ID);
+        mockDomain.setTags(new HashSet<>());
+
+        doReturn(Maybe.just(mockDomain)).when(domainService).findById(mockDomain.getId());
+        doReturn(Flowable.just(entrypoint, entrypoint2, defaultEntrypoint)).when(entrypointService).findAll(ORGANIZATION_ID);
+
+        final Response response = target("organizations")
+                .path(ORGANIZATION_ID)
+                .path("environments")
+                .path(ENVIRONMENT_ID)
+                .path("domains")
+                .path(DOMAIN_ID)
+                .path("entrypoints").request().get();
+
+        assertEquals(HttpStatusCode.OK_200, response.getStatus());
+        List<Entrypoint> entrypoints = readListEntity(response, Entrypoint.class);
+
+        assertEquals(1, entrypoints.size());
+        assertTrue(entrypoints.stream().anyMatch(e -> e.getId().equals(ENTRYPOINT_ID_DEFAULT)));
+    }
+
+    @Test
+    public void shouldGetEntrypoints_Entrypoint1And2() {
+
+        final Entrypoint entrypoint = new Entrypoint();
+        entrypoint.setId(ENTRYPOINT_ID1);
+        entrypoint.setName("entrypoint-1-name");
+        entrypoint.setTags(Arrays.asList(TAG_ID1));
+        entrypoint.setOrganizationId(ORGANIZATION_ID);
+
+        final Entrypoint entrypoint2 = new Entrypoint();
+        entrypoint2.setId(ENTRYPOINT_ID2);
+        entrypoint2.setName("entrypoint-2-name");
+        entrypoint2.setTags(Arrays.asList(TAG_ID2));
+        entrypoint2.setOrganizationId(ORGANIZATION_ID);
+
+        final Entrypoint defaultEntrypoint = new Entrypoint();
+        defaultEntrypoint.setId(ENTRYPOINT_ID_DEFAULT);
+        defaultEntrypoint.setName("Default");
+        defaultEntrypoint.setTags(Collections.emptyList());
+        defaultEntrypoint.setDefaultEntrypoint(true);
+        defaultEntrypoint.setOrganizationId(ORGANIZATION_ID);
+
+        Domain mockDomain = new Domain();
+        mockDomain.setId(DOMAIN_ID);
+        mockDomain.setTags(new HashSet<>(Arrays.asList(TAG_ID1, TAG_ID2)));
+
+        doReturn(Maybe.just(mockDomain)).when(domainService).findById(mockDomain.getId());
+        doReturn(Flowable.just(entrypoint, entrypoint2, defaultEntrypoint)).when(entrypointService).findAll(ORGANIZATION_ID);
+
+        final Response response = target("organizations")
+                .path(ORGANIZATION_ID)
+                .path("environments")
+                .path(ENVIRONMENT_ID)
+                .path("domains")
+                .path(DOMAIN_ID)
+                .path("entrypoints").request().get();
+
+        assertEquals(HttpStatusCode.OK_200, response.getStatus());
+        List<Entrypoint> entrypoints = readListEntity(response, Entrypoint.class);
+
+        assertEquals(2, entrypoints.size());
+        assertTrue(entrypoints.stream().anyMatch(e -> e.getId().equals(ENTRYPOINT_ID1)));
+        assertTrue(entrypoints.stream().anyMatch(e -> e.getId().equals(ENTRYPOINT_ID2)));
+    }
+
     private Domain buildDomainMock() {
         final Domain mockDomain = new Domain();
         mockDomain.setId("domain-id");
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/resources/EntrypointResourceTest.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/resources/EntrypointResourceTest.java
new file mode 100644
index 0000000000..41da8ada39
--- /dev/null
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/resources/EntrypointResourceTest.java
@@ -0,0 +1,133 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.gravitee.am.management.handlers.management.api.resources;
+
+import io.gravitee.am.identityprovider.api.User;
+import io.gravitee.am.management.handlers.management.api.JerseySpringTest;
+import io.gravitee.am.model.Entrypoint;
+import io.gravitee.am.service.exception.EntrypointNotFoundException;
+import io.gravitee.am.service.model.UpdateEntrypoint;
+import io.gravitee.common.http.HttpStatusCode;
+import io.reactivex.Single;
+import org.junit.Test;
+
+import javax.ws.rs.core.Response;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+
+/**
+ * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class EntrypointResourceTest extends JerseySpringTest {
+
+    public static final String ORGANIZATION_ID = "orga#1";
+    public static final String ENTRYPOINT_ID = "entrypoint#1";
+
+    @Test
+    public void shouldGetEntrypoint() {
+
+        final Entrypoint mockEntrypoint = new Entrypoint();
+        mockEntrypoint.setId(ENTRYPOINT_ID);
+        mockEntrypoint.setOrganizationId(ORGANIZATION_ID);
+        mockEntrypoint.setName("name");
+        mockEntrypoint.setDescription("description");
+        mockEntrypoint.setOrganizationId(ORGANIZATION_ID);
+        mockEntrypoint.setTags(Arrays.asList("tag#1", "tag#2"));
+        mockEntrypoint.setCreatedAt(new Date());
+        mockEntrypoint.setUpdatedAt(new Date());
+
+        doReturn(Single.just(mockEntrypoint)).when(entrypointService).findById(ENTRYPOINT_ID, ORGANIZATION_ID);
+
+        final Response response = target("organizations")
+                .path(ORGANIZATION_ID)
+                .path("entrypoints").path(ENTRYPOINT_ID).request().get();
+        assertEquals(HttpStatusCode.OK_200, response.getStatus());
+
+        final Entrypoint entrypoint = readEntity(response, Entrypoint.class);
+        assertEquals(mockEntrypoint.getId(), entrypoint.getId());
+        assertEquals(mockEntrypoint.getOrganizationId(), entrypoint.getOrganizationId());
+        assertEquals(mockEntrypoint.getName(), entrypoint.getName());
+        assertEquals(mockEntrypoint.getDescription(), entrypoint.getDescription());
+        assertEquals(mockEntrypoint.getUrl(), entrypoint.getUrl());
+        assertEquals(mockEntrypoint.getTags(), entrypoint.getTags());
+        assertEquals(mockEntrypoint.getCreatedAt(), entrypoint.getCreatedAt());
+        assertEquals(mockEntrypoint.getUpdatedAt(), entrypoint.getUpdatedAt());
+    }
+
+    @Test
+    public void shouldNotGetEntrypoint_notFound() {
+
+        doReturn(Single.error(new EntrypointNotFoundException(ENTRYPOINT_ID))).when(entrypointService).findById(ENTRYPOINT_ID, ORGANIZATION_ID);
+
+        final Response response = target("organizations")
+                .path(ORGANIZATION_ID)
+                .path("entrypoints").path(ENTRYPOINT_ID).request().get();
+
+        assertEquals(HttpStatusCode.NOT_FOUND_404, response.getStatus());
+    }
+
+    @Test
+    public void shouldUpdateEntrypoint() {
+
+        UpdateEntrypoint updateEntrypoint = new UpdateEntrypoint();
+        updateEntrypoint.setName("name");
+        updateEntrypoint.setUrl("https://auth.company.com");
+        updateEntrypoint.setTags(Collections.emptyList());
+
+        final Entrypoint mockEntrypoint = new Entrypoint();
+        mockEntrypoint.setId(ENTRYPOINT_ID);
+        mockEntrypoint.setOrganizationId(ORGANIZATION_ID);
+        mockEntrypoint.setName("name");
+
+        doReturn(Single.just(mockEntrypoint)).when(entrypointService).update(eq(ENTRYPOINT_ID), eq(ORGANIZATION_ID), any(UpdateEntrypoint.class), any(User.class));
+
+        final Response response = put(target("organizations")
+                .path(ORGANIZATION_ID)
+                .path("entrypoints").path(ENTRYPOINT_ID), updateEntrypoint);
+
+        assertEquals(HttpStatusCode.OK_200, response.getStatus());
+
+        final Entrypoint entrypoint = readEntity(response, Entrypoint.class);
+        assertEquals(mockEntrypoint.getId(), entrypoint.getId());
+        assertEquals(mockEntrypoint.getOrganizationId(), entrypoint.getOrganizationId());
+        assertEquals(mockEntrypoint.getName(), entrypoint.getName());
+    }
+
+    @Test
+    public void shouldNotUpdateEntrypoint_notFound() {
+
+        UpdateEntrypoint updateEntrypoint = new UpdateEntrypoint();
+        updateEntrypoint.setName("name");
+        updateEntrypoint.setUrl("https://auth.company.com");
+        updateEntrypoint.setTags(Collections.emptyList());
+
+        doReturn(Single.error(new EntrypointNotFoundException(ENTRYPOINT_ID))).when(entrypointService).update(eq(ENTRYPOINT_ID), eq(ORGANIZATION_ID), any(UpdateEntrypoint.class), any(User.class));
+
+        final Response response = put(target("organizations")
+                .path(ORGANIZATION_ID)
+                .path("entrypoints").path(ENTRYPOINT_ID), updateEntrypoint);
+
+        assertEquals(HttpStatusCode.NOT_FOUND_404, response.getStatus());
+    }
+}
diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/resources/EntrypointsResourceTest.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/resources/EntrypointsResourceTest.java
new file mode 100644
index 0000000000..829c1c11c8
--- /dev/null
+++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/resources/EntrypointsResourceTest.java
@@ -0,0 +1,103 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.gravitee.am.management.handlers.management.api.resources;
+
+import io.gravitee.am.identityprovider.api.User;
+import io.gravitee.am.management.handlers.management.api.JerseySpringTest;
+import io.gravitee.am.model.Entrypoint;
+import io.gravitee.am.service.exception.TechnicalManagementException;
+import io.gravitee.am.service.model.NewEntrypoint;
+import io.gravitee.common.http.HttpStatusCode;
+import io.reactivex.Flowable;
+import io.reactivex.Single;
+import org.junit.Test;
+
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doReturn;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class EntrypointsResourceTest extends JerseySpringTest {
+
+    public static final String ORGANIZATION_ID = "orga-1";
+    public static final String ENTRYPOINT_ID = "entrypoint-1";
+
+    @Test
+    public void shouldGetEntrypoints() {
+        final Entrypoint entrypoint = new Entrypoint();
+        entrypoint.setId(ENTRYPOINT_ID);
+        entrypoint.setName("entrypoint-1-name");
+        entrypoint.setOrganizationId(ORGANIZATION_ID);
+
+        final Entrypoint entrypoint2 = new Entrypoint();
+        entrypoint2.setId("entrypoint#2");
+        entrypoint2.setName("entrypoint-2-name");
+        entrypoint2.setOrganizationId(ORGANIZATION_ID);
+
+        doReturn(Flowable.just(entrypoint, entrypoint2)).when(entrypointService).findAll(ORGANIZATION_ID);
+
+        final Response response = target("organizations")
+                .path(ORGANIZATION_ID)
+                .path("entrypoints").request().get();
+        assertEquals(HttpStatusCode.OK_200, response.getStatus());
+
+        final List<Entrypoint> responseEntity = readEntity(response, List.class);
+        assertEquals(2, responseEntity.size());
+    }
+
+    @Test
+    public void shouldGetEntrypoints_technicalManagementException() {
+        doReturn(Flowable.error(new TechnicalManagementException("error occurs"))).when(entrypointService).findAll(anyString());
+
+        final Response response = target("organizations")
+                .path(ORGANIZATION_ID)
+                .path("entrypoints").request().get();
+        assertEquals(HttpStatusCode.INTERNAL_SERVER_ERROR_500, response.getStatus());
+    }
+
+    @Test
+    public void shouldCreate() {
+        NewEntrypoint newEntrypoint = new NewEntrypoint();
+        newEntrypoint.setName("name");
+        newEntrypoint.setUrl("https://auth.company.com");
+        newEntrypoint.setTags(Collections.emptyList());
+
+        Entrypoint entrypoint = new Entrypoint();
+        entrypoint.setId("entrypoint-1");
+        entrypoint.setName("name");
+
+        doReturn(Single.just(entrypoint)).when(entrypointService).create(eq(ORGANIZATION_ID), any(NewEntrypoint.class), any(User.class));
+
+        WebTarget path = target("organizations")
+                .path(ORGANIZATION_ID)
+                .path("entrypoints");
+
+        final Response response = post(path, newEntrypoint);
+        assertEquals(HttpStatusCode.CREATED_201, response.getStatus());
+        assertEquals(path.getUri().toString() + "/"+ entrypoint.getId(), response.getHeaderString(HttpHeaders.LOCATION));
+    }
+}
diff --git a/gravitee-am-repository/gravitee-am-repository-mongodb/src/test/java/io/gravitee/am/repository/mongodb/management/MongoEntrypointRepositoryTest.java b/gravitee-am-repository/gravitee-am-repository-mongodb/src/test/java/io/gravitee/am/repository/mongodb/management/MongoEntrypointRepositoryTest.java
new file mode 100644
index 0000000000..9339947e1c
--- /dev/null
+++ b/gravitee-am-repository/gravitee-am-repository-mongodb/src/test/java/io/gravitee/am/repository/mongodb/management/MongoEntrypointRepositoryTest.java
@@ -0,0 +1,130 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.gravitee.am.repository.mongodb.management;
+
+import io.gravitee.am.model.Entrypoint;
+import io.gravitee.am.repository.management.api.EntrypointRepository;
+import io.reactivex.observers.TestObserver;
+import io.reactivex.subscribers.TestSubscriber;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+public class MongoEntrypointRepositoryTest extends AbstractManagementRepositoryTest {
+
+    public static final String ORGANIZATION_ID = "orga#1";
+
+    @Autowired
+    private EntrypointRepository entrypointRepository;
+
+    @Override
+    public String collectionName() {
+        return "entrypoints";
+    }
+
+    @Test
+    public void testFindAll() {
+
+        Entrypoint entrypoint = new Entrypoint();
+        entrypoint.setName("testName");
+        entrypoint.setDescription("Description");
+        entrypoint.setOrganizationId(ORGANIZATION_ID);
+        entrypointRepository.create(entrypoint).blockingGet();
+
+        TestSubscriber<Entrypoint> testObserver1 = entrypointRepository.findAll(ORGANIZATION_ID).test();
+        testObserver1.awaitTerminalEvent();
+
+        testObserver1.assertComplete();
+        testObserver1.assertNoErrors();
+        testObserver1.assertValueCount(1);
+    }
+
+    @Test
+    public void testFindById() {
+
+        Entrypoint entrypoint = new Entrypoint();
+        entrypoint.setName("testName");
+        Entrypoint entrypointCreated = entrypointRepository.create(entrypoint).blockingGet();
+
+        TestObserver<Entrypoint> testObserver = entrypointRepository.findById(entrypointCreated.getId()).test();
+        testObserver.awaitTerminalEvent();
+
+        testObserver.assertComplete();
+        testObserver.assertNoErrors();
+        testObserver.assertValue(d -> d.getName().equals("testName"));
+    }
+
+    @Test
+    public void testNotFoundById() {
+
+        entrypointRepository.findById("test").test().assertEmpty();
+    }
+
+    @Test
+    public void testCreate() {
+
+        Entrypoint entrypoint = new Entrypoint();
+        entrypoint.setName("testName");
+
+        TestObserver<Entrypoint> testObserver = entrypointRepository.create(entrypoint).test();
+        testObserver.awaitTerminalEvent();
+
+        testObserver.assertComplete();
+        testObserver.assertNoErrors();
+        testObserver.assertValue(entrypointCreated -> entrypointCreated.getName().equals(entrypoint.getName()));
+    }
+
+    @Test
+    public void testUpdate() {
+
+        Entrypoint entrypoint = new Entrypoint();
+        entrypoint.setName("testName");
+        Entrypoint entrypointCreated = entrypointRepository.create(entrypoint).blockingGet();
+
+        Entrypoint updatedEntrypoint = new Entrypoint();
+        updatedEntrypoint.setId(entrypointCreated.getId());
+        updatedEntrypoint.setName("testUpdatedName");
+
+        TestObserver<Entrypoint> testObserver = entrypointRepository.update(updatedEntrypoint).test();
+        testObserver.awaitTerminalEvent();
+
+        testObserver.assertComplete();
+        testObserver.assertNoErrors();
+        testObserver.assertValue(t -> t.getName().equals(updatedEntrypoint.getName()));
+    }
+
+    @Test
+    public void testDelete() {
+
+        Entrypoint entrypoint = new Entrypoint();
+        entrypoint.setName("testName");
+        Entrypoint entrypointCreated = entrypointRepository.create(entrypoint).blockingGet();
+
+        TestObserver<Entrypoint> testObserver = entrypointRepository.findById(entrypointCreated.getId()).test();
+        testObserver.awaitTerminalEvent();
+        testObserver.assertComplete();
+        testObserver.assertNoErrors();
+        testObserver.assertValue(t -> t.getName().equals(entrypoint.getName()));
+
+        TestObserver testObserver1 = entrypointRepository.delete(entrypointCreated.getId()).test();
+        testObserver1.awaitTerminalEvent();
+
+        entrypointRepository.findById(entrypointCreated.getId()).test().assertEmpty();
+    }
+}
\ No newline at end of file
diff --git a/gravitee-am-service/src/test/java/io/gravitee/am/service/EntrypointServiceTest.java b/gravitee-am-service/src/test/java/io/gravitee/am/service/EntrypointServiceTest.java
new file mode 100644
index 0000000000..68eb2b8809
--- /dev/null
+++ b/gravitee-am-service/src/test/java/io/gravitee/am/service/EntrypointServiceTest.java
@@ -0,0 +1,410 @@
+/**
+ * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.gravitee.am.service;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.gravitee.am.common.audit.EventType;
+import io.gravitee.am.identityprovider.api.DefaultUser;
+import io.gravitee.am.model.Entrypoint;
+import io.gravitee.am.model.ReferenceType;
+import io.gravitee.am.reporter.api.audit.model.Audit;
+import io.gravitee.am.repository.exceptions.TechnicalException;
+import io.gravitee.am.repository.management.api.EntrypointRepository;
+import io.gravitee.am.service.exception.EntrypointNotFoundException;
+import io.gravitee.am.service.exception.InvalidEntrypointException;
+import io.gravitee.am.service.impl.EntrypointServiceImpl;
+import io.gravitee.am.service.model.NewEntrypoint;
+import io.gravitee.am.service.model.UpdateEntrypoint;
+import io.reactivex.Completable;
+import io.reactivex.Maybe;
+import io.reactivex.Single;
+import io.reactivex.observers.TestObserver;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+/**
+ * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
+ * @author GraviteeSource Team
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class EntrypointServiceTest {
+
+    public static final String ENTRYPOINT_ID = "entrypoint#1";
+    public static final String ORGANIZATION_ID = "orga#1";
+    public static final String USER_ID = "user#1";
+
+    @Mock
+    private EntrypointRepository entrypointRepository;
+
+    @Mock
+    private AuditService auditService;
+
+    private EntrypointService cut;
+
+    @Before
+    public void before() {
+
+        cut = new EntrypointServiceImpl(entrypointRepository, auditService);
+    }
+
+    @Test
+    public void shouldFindById() {
+
+        Entrypoint entrypoint = new Entrypoint();
+        when(entrypointRepository.findById(ENTRYPOINT_ID, ORGANIZATION_ID)).thenReturn(Maybe.just(entrypoint));
+
+        TestObserver<Entrypoint> obs = cut.findById(ENTRYPOINT_ID, ORGANIZATION_ID).test();
+
+        obs.awaitTerminalEvent();
+        obs.assertComplete();
+        obs.assertValue(entrypoint);
+    }
+
+    @Test
+    public void shouldFindById_notExistingEntrypoint() {
+
+        when(entrypointRepository.findById(ENTRYPOINT_ID, ORGANIZATION_ID)).thenReturn(Maybe.empty());
+
+        TestObserver<Entrypoint> obs = cut.findById(ENTRYPOINT_ID, ORGANIZATION_ID).test();
+
+        obs.awaitTerminalEvent();
+        obs.assertError(EntrypointNotFoundException.class);
+    }
+
+    @Test
+    public void shouldFindById_technicalException() {
+
+        when(entrypointRepository.findById(ENTRYPOINT_ID, ORGANIZATION_ID)).thenReturn(Maybe.error(TechnicalException::new));
+
+        TestObserver<Entrypoint> obs = cut.findById(ENTRYPOINT_ID, ORGANIZATION_ID).test();
+
+        obs.awaitTerminalEvent();
+        obs.assertError(TechnicalException.class);
+    }
+
+    @Test
+    public void shouldCreateDefault() {
+
+        when(entrypointRepository.create(any(Entrypoint.class))).thenAnswer(i -> Single.just(i.getArgument(0)));
+
+        TestObserver<Entrypoint> obs = cut.createDefault(ORGANIZATION_ID).test();
+
+        obs.awaitTerminalEvent();
+        obs.assertValue(entrypoint -> entrypoint.getId() != null
+                && entrypoint.isDefaultEntrypoint() && entrypoint.getOrganizationId().equals(ORGANIZATION_ID));
+
+        verify(auditService, times(1)).report(argThat(builder -> {
+            Audit audit = builder.build(new ObjectMapper());
+            assertEquals(EventType.ENTRYPOINT_CREATED, audit.getType());
+            assertEquals(ReferenceType.ORGANIZATION, audit.getReferenceType());
+            assertEquals(ORGANIZATION_ID, audit.getReferenceId());
+            assertEquals("system", audit.getActor().getId());
+
+            return true;
+        }));
+    }
+
+    @Test
+    public void shouldCreate() {
+
+        DefaultUser user = new DefaultUser("test");
+        user.setId(USER_ID);
+
+        NewEntrypoint newEntrypoint = new NewEntrypoint();
+        newEntrypoint.setName("name");
+        newEntrypoint.setDescription("description");
+        newEntrypoint.setTags(Arrays.asList("tag#1", "tags#2"));
+        newEntrypoint.setUrl("https://auth.company.com");
+
+        when(entrypointRepository.create(any(Entrypoint.class))).thenAnswer(i -> Single.just(i.getArgument(0)));
+
+        TestObserver<Entrypoint> obs = cut.create(ORGANIZATION_ID, newEntrypoint, user).test();
+
+        obs.awaitTerminalEvent();
+        obs.assertValue(entrypoint -> entrypoint.getId() != null
+                && !entrypoint.isDefaultEntrypoint()
+                && entrypoint.getOrganizationId().equals(ORGANIZATION_ID)
+                && entrypoint.getName().equals(newEntrypoint.getName())
+                && entrypoint.getDescription().equals(newEntrypoint.getDescription())
+                && entrypoint.getTags().equals(newEntrypoint.getTags())
+                && entrypoint.getUrl().equals(newEntrypoint.getUrl()));
+
+        verify(auditService, times(1)).report(argThat(builder -> {
+            Audit audit = builder.build(new ObjectMapper());
+            assertEquals(EventType.ENTRYPOINT_CREATED, audit.getType());
+            assertEquals(ReferenceType.ORGANIZATION, audit.getReferenceType());
+            assertEquals(ORGANIZATION_ID, audit.getReferenceId());
+            assertEquals(user.getId(), audit.getActor().getId());
+
+            return true;
+        }));
+    }
+
+    @Test
+    public void shouldNotCreate_badUrl() {
+
+        DefaultUser user = new DefaultUser("test");
+        user.setId(USER_ID);
+
+        NewEntrypoint newEntrypoint = new NewEntrypoint();
+        newEntrypoint.setName("name");
+        newEntrypoint.setDescription("description");
+        newEntrypoint.setTags(Arrays.asList("tag#1", "tags#2"));
+        newEntrypoint.setUrl("invalid");
+
+        when(entrypointRepository.create(any(Entrypoint.class))).thenAnswer(i -> Single.just(i.getArgument(0)));
+
+        TestObserver<Entrypoint> obs = cut.create(ORGANIZATION_ID, newEntrypoint, user).test();
+
+        obs.awaitTerminalEvent();
+        obs.assertError(InvalidEntrypointException.class);
+
+        verifyZeroInteractions(auditService);
+    }
+
+    @Test
+    public void shouldUpdate() {
+
+        DefaultUser user = new DefaultUser("test");
+        user.setId(USER_ID);
+
+        Entrypoint existingEntrypoint = new Entrypoint();
+        existingEntrypoint.setId(ENTRYPOINT_ID);
+        existingEntrypoint.setOrganizationId(ORGANIZATION_ID);
+
+        UpdateEntrypoint updateEntrypoint = new UpdateEntrypoint();
+        updateEntrypoint.setName("name");
+        updateEntrypoint.setDescription("description");
+        updateEntrypoint.setTags(Arrays.asList("tag#1", "tags#2"));
+        updateEntrypoint.setUrl("https://auth.company.com");
+
+        when(entrypointRepository.findById(ENTRYPOINT_ID, ORGANIZATION_ID)).thenReturn(Maybe.just(existingEntrypoint));
+        when(entrypointRepository.update(any(Entrypoint.class))).thenAnswer(i -> Single.just(i.getArgument(0)));
+
+        TestObserver<Entrypoint> obs = cut.update(ENTRYPOINT_ID, ORGANIZATION_ID, updateEntrypoint, user).test();
+
+        obs.awaitTerminalEvent();
+        obs.assertValue(entrypoint -> entrypoint.getId() != null
+                && !entrypoint.isDefaultEntrypoint()
+                && entrypoint.getOrganizationId().equals(ORGANIZATION_ID)
+                && entrypoint.getName().equals(updateEntrypoint.getName())
+                && entrypoint.getDescription().equals(updateEntrypoint.getDescription())
+                && entrypoint.getTags().equals(updateEntrypoint.getTags())
+                && entrypoint.getUrl().equals(updateEntrypoint.getUrl())
+                && entrypoint.getUpdatedAt() != null);
+
+        verify(auditService, times(1)).report(argThat(builder -> {
+            Audit audit = builder.build(new ObjectMapper());
+            assertEquals(EventType.ENTRYPOINT_UPDATED, audit.getType());
+            assertEquals(ReferenceType.ORGANIZATION, audit.getReferenceType());
+            assertEquals(ORGANIZATION_ID, audit.getReferenceId());
+            assertEquals(user.getId(), audit.getActor().getId());
+
+            return true;
+        }));
+    }
+
+    @Test
+    public void shouldNotUpdate_badUrl() {
+
+        DefaultUser user = new DefaultUser("test");
+        user.setId(USER_ID);
+
+        Entrypoint existingEntrypoint = new Entrypoint();
+        existingEntrypoint.setId(ENTRYPOINT_ID);
+        existingEntrypoint.setOrganizationId(ORGANIZATION_ID);
+
+        UpdateEntrypoint updateEntrypoint = new UpdateEntrypoint();
+        updateEntrypoint.setName("name");
+        updateEntrypoint.setDescription("description");
+        updateEntrypoint.setTags(Arrays.asList("tag#1", "tags#2"));
+        updateEntrypoint.setUrl("invalid");
+
+        when(entrypointRepository.findById(ENTRYPOINT_ID, ORGANIZATION_ID)).thenReturn(Maybe.just(existingEntrypoint));
+        when(entrypointRepository.update(any(Entrypoint.class))).thenAnswer(i -> Single.just(i.getArgument(0)));
+
+        TestObserver<Entrypoint> obs = cut.update(ENTRYPOINT_ID, ORGANIZATION_ID, updateEntrypoint, user).test();
+
+        obs.awaitTerminalEvent();
+        obs.assertError(InvalidEntrypointException.class);
+
+        verifyZeroInteractions(auditService);
+    }
+
+    @Test
+    public void shouldNotUpdate_notExistingEntrypoint() {
+
+        DefaultUser user = new DefaultUser("test");
+        user.setId(USER_ID);
+
+        when(entrypointRepository.findById(ENTRYPOINT_ID, ORGANIZATION_ID)).thenReturn(Maybe.empty());
+
+        TestObserver<Entrypoint> obs = cut.update(ENTRYPOINT_ID, ORGANIZATION_ID, new UpdateEntrypoint(), user).test();
+
+        obs.awaitTerminalEvent();
+        obs.assertError(EntrypointNotFoundException.class);
+
+        verifyZeroInteractions(auditService);
+    }
+
+    @Test
+    public void shouldUpdateDefault_onlyUrl() {
+
+        DefaultUser user = new DefaultUser("test");
+        user.setId(USER_ID);
+
+        Entrypoint existingEntrypoint = new Entrypoint();
+        existingEntrypoint.setId(ENTRYPOINT_ID);
+        existingEntrypoint.setOrganizationId(ORGANIZATION_ID);
+        existingEntrypoint.setName("name");
+        existingEntrypoint.setDescription("description");
+        existingEntrypoint.setTags(Arrays.asList("tag#1", "tags#2"));
+        existingEntrypoint.setUrl("https://current.com");
+
+        UpdateEntrypoint updateEntrypoint = new UpdateEntrypoint();
+        updateEntrypoint.setName("name");
+        updateEntrypoint.setDescription("description");
+        updateEntrypoint.setTags(Arrays.asList("tag#1", "tags#2"));
+        updateEntrypoint.setUrl("https://changed.com");
+
+        when(entrypointRepository.findById(ENTRYPOINT_ID, ORGANIZATION_ID)).thenReturn(Maybe.just(existingEntrypoint));
+        when(entrypointRepository.update(any(Entrypoint.class))).thenAnswer(i -> Single.just(i.getArgument(0)));
+
+        TestObserver<Entrypoint> obs = cut.update(ENTRYPOINT_ID, ORGANIZATION_ID, updateEntrypoint, user).test();
+
+        obs.awaitTerminalEvent();
+        obs.assertValue(entrypoint -> entrypoint.getId() != null
+                && !entrypoint.isDefaultEntrypoint()
+                && entrypoint.getOrganizationId().equals(ORGANIZATION_ID)
+                && entrypoint.getName().equals(updateEntrypoint.getName())
+                && entrypoint.getDescription().equals(updateEntrypoint.getDescription())
+                && entrypoint.getTags().equals(updateEntrypoint.getTags())
+                && entrypoint.getUrl().equals(updateEntrypoint.getUrl())
+                && entrypoint.getUpdatedAt() != null);
+
+        verify(auditService, times(1)).report(argThat(builder -> {
+            Audit audit = builder.build(new ObjectMapper());
+            assertEquals(EventType.ENTRYPOINT_UPDATED, audit.getType());
+            assertEquals(ReferenceType.ORGANIZATION, audit.getReferenceType());
+            assertEquals(ORGANIZATION_ID, audit.getReferenceId());
+            assertEquals(user.getId(), audit.getActor().getId());
+
+            return true;
+        }));
+    }
+
+    @Test
+    public void shouldNotUpdateDefault_onlyUrl() {
+
+        DefaultUser user = new DefaultUser("test");
+        user.setId(USER_ID);
+
+        Entrypoint existingEntrypoint = new Entrypoint();
+        existingEntrypoint.setId(ENTRYPOINT_ID);
+        existingEntrypoint.setOrganizationId(ORGANIZATION_ID);
+        existingEntrypoint.setName("name");
+        existingEntrypoint.setDescription("description");
+        existingEntrypoint.setTags(Collections.emptyList());
+        existingEntrypoint.setUrl("https://current.com");
+        existingEntrypoint.setDefaultEntrypoint(true);
+
+        UpdateEntrypoint updateEntrypoint = new UpdateEntrypoint();
+        updateEntrypoint.setName("name");
+        updateEntrypoint.setDescription("description");
+        updateEntrypoint.setTags(Collections.emptyList());
+        updateEntrypoint.setUrl("https://changed.com");
+
+        when(entrypointRepository.findById(ENTRYPOINT_ID, ORGANIZATION_ID)).thenReturn(Maybe.just(existingEntrypoint));
+        when(entrypointRepository.update(any(Entrypoint.class))).thenAnswer(i -> Single.just(i.getArgument(0)));
+
+        TestObserver<Entrypoint> obs;
+
+        updateEntrypoint.setName("updated");
+        obs = cut.update(ENTRYPOINT_ID, ORGANIZATION_ID, updateEntrypoint, user).test();
+        obs.awaitTerminalEvent();
+        obs.assertError(InvalidEntrypointException.class);
+
+        updateEntrypoint.setName(existingEntrypoint.getName());
+        updateEntrypoint.setDescription("updated");
+        obs = cut.update(ENTRYPOINT_ID, ORGANIZATION_ID, updateEntrypoint, user).test();
+        obs.awaitTerminalEvent();
+        obs.assertError(InvalidEntrypointException.class);
+
+        updateEntrypoint.setDescription(existingEntrypoint.getDescription());
+        updateEntrypoint.setTags(Arrays.asList("updated"));
+        obs = cut.update(ENTRYPOINT_ID, ORGANIZATION_ID, updateEntrypoint, user).test();
+        obs.awaitTerminalEvent();
+        obs.assertError(InvalidEntrypointException.class);
+
+        verifyZeroInteractions(auditService);
+    }
+
+    @Test
+    public void shouldDelete() {
+
+        DefaultUser user = new DefaultUser("test");
+        user.setId(USER_ID);
+
+        Entrypoint existingEntrypoint = new Entrypoint();
+        existingEntrypoint.setId(ENTRYPOINT_ID);
+        existingEntrypoint.setOrganizationId(ORGANIZATION_ID);
+
+        when(entrypointRepository.findById(ENTRYPOINT_ID, ORGANIZATION_ID)).thenReturn(Maybe.just(existingEntrypoint));
+        when(entrypointRepository.delete(ENTRYPOINT_ID)).thenReturn(Completable.complete());
+
+        TestObserver<Void> obs = cut.delete(ENTRYPOINT_ID, ORGANIZATION_ID, user).test();
+
+        obs.awaitTerminalEvent();
+        obs.assertComplete();
+
+        verify(auditService, times(1)).report(argThat(builder -> {
+            Audit audit = builder.build(new ObjectMapper());
+            assertEquals(EventType.ENTRYPOINT_DELETED, audit.getType());
+            assertEquals(ReferenceType.ORGANIZATION, audit.getReferenceType());
+            assertEquals(ORGANIZATION_ID, audit.getReferenceId());
+            assertEquals(user.getId(), audit.getActor().getId());
+
+            return true;
+        }));
+    }
+
+
+    @Test
+    public void shouldNotDelete_notExistingEntrypoint() {
+
+        DefaultUser user = new DefaultUser("test");
+        user.setId(USER_ID);
+
+        when(entrypointRepository.findById(ENTRYPOINT_ID, ORGANIZATION_ID)).thenReturn(Maybe.empty());
+
+        TestObserver<Void> obs = cut.delete(ENTRYPOINT_ID, ORGANIZATION_ID, user).test();
+
+        obs.awaitTerminalEvent();
+        obs.assertError(EntrypointNotFoundException.class);
+
+        verifyZeroInteractions(auditService);
+    }
+}
diff --git a/gravitee-am-service/src/test/java/io/gravitee/am/service/OrganizationServiceTest.java b/gravitee-am-service/src/test/java/io/gravitee/am/service/OrganizationServiceTest.java
index e726ebc50d..4e85de45bb 100644
--- a/gravitee-am-service/src/test/java/io/gravitee/am/service/OrganizationServiceTest.java
+++ b/gravitee-am-service/src/test/java/io/gravitee/am/service/OrganizationServiceTest.java
@@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import io.gravitee.am.common.audit.EventType;
 import io.gravitee.am.common.audit.Status;
 import io.gravitee.am.identityprovider.api.DefaultUser;
+import io.gravitee.am.model.Entrypoint;
 import io.gravitee.am.model.Organization;
 import io.gravitee.am.model.ReferenceType;
 import io.gravitee.am.reporter.api.audit.model.Audit;
@@ -62,6 +63,9 @@ public class OrganizationServiceTest {
     @Mock
     private RoleService roleService;
 
+    @Mock
+    private EntrypointService entrypointService;
+
     @Mock
     private AuditService auditService;
 
@@ -70,7 +74,7 @@ public class OrganizationServiceTest {
     @Before
     public void before() {
 
-        cut = new OrganizationServiceImpl(organizationRepository, roleService, auditService);
+        cut = new OrganizationServiceImpl(organizationRepository, roleService, entrypointService, auditService);
     }
 
     @Test
@@ -117,6 +121,7 @@ public class OrganizationServiceTest {
         when(organizationRepository.count()).thenReturn(Single.just(0L));
         when(organizationRepository.create(argThat(organization -> organization.getId().equals(Organization.DEFAULT)))).thenReturn(Single.just(defaultOrganization));
         when(roleService.createDefaultRoles("DEFAULT")).thenReturn(Completable.complete());
+        when(entrypointService.createDefault("DEFAULT")).thenReturn(Single.just(new Entrypoint()));
 
         TestObserver<Organization> obs = cut.createDefault().test();
 
@@ -183,6 +188,7 @@ public class OrganizationServiceTest {
         when(organizationRepository.findById(ORGANIZATION_ID)).thenReturn(Maybe.empty());
         when(organizationRepository.create(argThat(organization -> organization.getId().equals(ORGANIZATION_ID)))).thenAnswer(i -> Single.just(i.getArgument(0)));
         when(roleService.createDefaultRoles(ORGANIZATION_ID)).thenReturn(Completable.complete());
+        when(entrypointService.createDefault(ORGANIZATION_ID)).thenReturn(Single.just(new Entrypoint()));
 
         NewOrganization newOrganization = new NewOrganization();
         newOrganization.setName("TestName");