diff --git a/app/portainer/environments/environment.service/create.ts b/app/portainer/environments/environment.service/create.ts
new file mode 100644
index 000000000..ff6375ea1
--- /dev/null
+++ b/app/portainer/environments/environment.service/create.ts
@@ -0,0 +1,174 @@
+import PortainerError from '@/portainer/error';
+import axios, { parseAxiosError } from '@/portainer/services/axios';
+
+import {
+  Environment,
+  EnvironmentGroupId,
+  EnvironmentCreationTypes,
+  TagId,
+} from '../types';
+
+import { arrayToJson, buildUrl, json2formData } from './utils';
+
+export async function createLocalEndpoint(
+  name = 'local',
+  url = '',
+  publicUrl = '',
+  groupId: EnvironmentGroupId = 1,
+  tagIds: TagId[] = []
+) {
+  let endpointUrl = url;
+  if (endpointUrl !== '') {
+    if (endpointUrl.includes('//./pipe/')) {
+      endpointUrl = `unix://${url}`;
+    } else {
+      // Windows named pipe
+      endpointUrl = `npipe://${url}`;
+    }
+  }
+
+  try {
+    return await createEndpoint(
+      name,
+      EnvironmentCreationTypes.LocalDockerEnvironment,
+      { url: endpointUrl, publicUrl, groupId, tagIds }
+    );
+  } catch (err) {
+    throw new PortainerError('Unable to create environment', err as Error);
+  }
+}
+
+export async function createLocalKubernetesEndpoint(
+  name = 'local',
+  tagIds: TagId[] = []
+) {
+  try {
+    return await createEndpoint(
+      name,
+      EnvironmentCreationTypes.LocalKubernetesEnvironment,
+      { tagIds, groupId: 1, tls: { skipClientVerify: true, skipVerify: true } }
+    );
+  } catch (err) {
+    throw new PortainerError('Unable to create environment', err as Error);
+  }
+}
+
+export async function createAzureEndpoint(
+  name: string,
+  applicationId: string,
+  tenantId: string,
+  authenticationKey: string,
+  groupId: EnvironmentGroupId,
+  tagIds: TagId[]
+) {
+  try {
+    await createEndpoint(name, EnvironmentCreationTypes.AzureEnvironment, {
+      groupId,
+      tagIds,
+      azure: { applicationId, tenantId, authenticationKey },
+    });
+  } catch (err) {
+    throw new PortainerError('Unable to connect to Azure', err as Error);
+  }
+}
+
+interface TLSSettings {
+  skipVerify?: boolean;
+  skipClientVerify?: boolean;
+  caCertFile?: File;
+  certFile?: File;
+  keyFile?: File;
+}
+
+interface AzureSettings {
+  applicationId: string;
+  tenantId: string;
+  authenticationKey: string;
+}
+
+interface EndpointOptions {
+  url?: string;
+  publicUrl?: string;
+  groupId?: EnvironmentGroupId;
+  tagIds?: TagId[];
+  checkinInterval?: number;
+  azure?: AzureSettings;
+  tls?: TLSSettings;
+}
+
+export async function createRemoteEndpoint(
+  name: string,
+  creationType: EnvironmentCreationTypes,
+  options?: EndpointOptions
+) {
+  let endpointUrl = options?.url;
+  if (creationType !== EnvironmentCreationTypes.EdgeAgentEnvironment) {
+    endpointUrl = `tcp://${endpointUrl}`;
+  }
+
+  try {
+    return await createEndpoint(name, creationType, {
+      ...options,
+      url: endpointUrl,
+    });
+  } catch (err) {
+    throw new PortainerError('Unable to create environment', err as Error);
+  }
+}
+
+async function createEndpoint(
+  name: string,
+  creationType: EnvironmentCreationTypes,
+  options?: EndpointOptions
+) {
+  let payload: Record<string, unknown> = {
+    Name: name,
+    EndpointCreationType: creationType,
+  };
+
+  if (options) {
+    payload = {
+      ...payload,
+      URL: options.url,
+      PublicURL: options.publicUrl,
+      GroupID: options.groupId,
+      TagIds: arrayToJson(options.tagIds),
+      CheckinInterval: options.checkinInterval,
+    };
+
+    const { tls, azure } = options;
+
+    if (tls) {
+      payload = {
+        ...payload,
+        TLS: true,
+        TLSSkipVerify: tls.skipVerify,
+        TLSSkipClientVerify: tls.skipClientVerify,
+        TLSCACertFile: tls.caCertFile,
+        TLSCertFile: tls.certFile,
+        TLSKeyFile: tls.keyFile,
+      };
+    }
+
+    if (azure) {
+      payload = {
+        ...payload,
+        AzureApplicationID: azure.applicationId,
+        AzureTenantID: azure.tenantId,
+        AzureAuthenticationKey: azure.authenticationKey,
+      };
+    }
+  }
+
+  const formPayload = json2formData(payload);
+  try {
+    const { data: endpoint } = await axios.post<Environment>(
+      buildUrl(),
+      formPayload
+    );
+
+    return endpoint;
+  } catch (e) {
+    throw parseAxiosError(e as Error);
+  }
+}
diff --git a/app/portainer/environments/environment.service/index.ts b/app/portainer/environments/environment.service/index.ts
new file mode 100644
index 000000000..563130d28
--- /dev/null
+++ b/app/portainer/environments/environment.service/index.ts
@@ -0,0 +1,224 @@
+import axios, { parseAxiosError } from '@/portainer/services/axios';
+
+import {
+  Environment,
+  EnvironmentGroupId,
+  EnvironmentId,
+  EnvironmentType,
+  EnvironmentSettings,
+  TagId,
+  TeamId,
+  UserId,
+} from '../types';
+
+import { arrayToJson, buildUrl } from './utils';
+
+interface EndpointsQuery {
+  search?: string;
+  types?: EnvironmentType[];
+  tagIds?: TagId[];
+  endpointIds?: EnvironmentId[];
+  tagsPartialMatch?: boolean;
+  groupId?: EnvironmentGroupId;
+}
+
+export async function getEndpoints(
+  start: number,
+  limit: number,
+  { types, tagIds, endpointIds, ...query }: EndpointsQuery = {}
+) {
+  if (tagIds && tagIds.length === 0) {
+    return { totalCount: 0, value: <Environment[]>[] };
+  }
+
+  const url = buildUrl();
+
+  const params: Record<string, unknown> = { start, limit, ...query };
+
+  if (types) {
+    params.types = arrayToJson(types);
+  }
+
+  if (tagIds) {
+    params.tagIds = arrayToJson(tagIds);
+  }
+
+  if (endpointIds) {
+    params.endpointIds = arrayToJson(endpointIds);
+  }
+
+  try {
+    const response = await axios.get<Environment[]>(url, { params });
+
+    const totalCount = response.headers['X-Total-Count'];
+
+    return { totalCount: parseInt(totalCount, 10), value: response.data };
+  } catch (e) {
+    throw parseAxiosError(e as Error);
+  }
+}
+
+export async function getEndpoint(id: EnvironmentId) {
+  try {
+    const { data: endpoint } = await axios.get<Environment>(buildUrl(id));
+    return endpoint;
+  } catch (e) {
+    throw parseAxiosError(e as Error);
+  }
+}
+
+export async function snapshotEndpoints() {
+  try {
+    await axios.post<void>(buildUrl(undefined, 'snapshot'));
+  } catch (e) {
+    throw parseAxiosError(e as Error);
+  }
+}
+
+export async function snapshotEndpoint(id: EnvironmentId) {
+  try {
+    await axios.post<void>(buildUrl(id, 'snapshot'));
+  } catch (e) {
+    throw parseAxiosError(e as Error);
+  }
+}
+
+export async function endpointsByGroup(
+  start: number,
+  limit: number,
+  search: string,
+  groupId: EnvironmentGroupId
+) {
+  return getEndpoints(start, limit, { search, groupId });
+}
+
+export async function disassociateEndpoint(id: EnvironmentId) {
+  try {
+    await axios.delete(buildUrl(id, 'association'));
+  } catch (e) {
+    throw parseAxiosError(e as Error);
+  }
+}
+
+interface UpdatePayload {
+  TLSCACert?: File;
+  TLSCert?: File;
+  TLSKey?: File;
+
+  Name: string;
+  PublicURL: string;
+  GroupID: EnvironmentGroupId;
+  TagIds: TagId[];
+
+  EdgeCheckinInterval: number;
+
+  TLS: boolean;
+  TLSSkipVerify: boolean;
+  TLSSkipClientVerify: boolean;
+  AzureApplicationID: string;
+  AzureTenantID: string;
+  AzureAuthenticationKey: string;
+}
+
+async function uploadTLSFilesForEndpoint(
+  id: EnvironmentId,
+  tlscaCert?: File,
+  tlsCert?: File,
+  tlsKey?: File
+) {
+  await Promise.all([
+    uploadCert('ca', tlscaCert),
+    uploadCert('cert', tlsCert),
+    uploadCert('key', tlsKey),
+  ]);
+
+  function uploadCert(type: 'ca' | 'cert' | 'key', cert?: File) {
+    if (!cert) {
+      return null;
+    }
+    try {
+      return axios.post<void>(`upload/tls/${type}`, cert, {
+        params: { folder: id },
+      });
+    } catch (e) {
+      throw parseAxiosError(e as Error);
+    }
+  }
+}
+
+export async function updateEndpoint(
+  id: EnvironmentId,
+  payload: UpdatePayload
+) {
+  try {
+    await uploadTLSFilesForEndpoint(
+      id,
+      payload.TLSCACert,
+      payload.TLSCert,
+      payload.TLSKey
+    );
+
+    const { data: endpoint } = await axios.put<Environment>(
+      buildUrl(id),
+      payload
+    );
+
+    return endpoint;
+  } catch (e) {
+    throw parseAxiosError(e as Error, 'Unable to update environment');
+  }
+}
+
+export async function deleteEndpoint(id: EnvironmentId) {
+  try {
+    await axios.delete(buildUrl(id));
+  } catch (e) {
+    throw parseAxiosError(e as Error);
+  }
+}
+
+export async function updatePoolAccess(
+  id: EnvironmentId,
+  resourcePool: string,
+  usersToAdd: UserId[],
+  teamsToAdd: TeamId[],
+  usersToRemove: UserId[],
+  teamsToRemove: TeamId[]
+) {
+  try {
+    await axios.put<void>(`${buildUrl(id, 'pools')}/${resourcePool}/access`, {
+      usersToAdd,
+      teamsToAdd,
+      usersToRemove,
+      teamsToRemove,
+    });
+  } catch (e) {
+    throw parseAxiosError(e as Error);
+  }
+}
+
+export async function forceUpdateService(
+  id: EnvironmentId,
+  serviceID: string,
+  pullImage: boolean
+) {
+  try {
+    await axios.put(buildUrl(id, 'forceupdateservice'), {
+      serviceID,
+      pullImage,
+    });
+  } catch (e) {
+    throw parseAxiosError(e as Error);
+  }
+}
+
+export async function updateSettings(
+  id: EnvironmentId,
+  settings: EnvironmentSettings
+) {
+  try {
+    await axios.put(buildUrl(id, 'settings'), settings);
+  } catch (e) {
+    throw parseAxiosError(e as Error);
+  }
+}
diff --git a/app/portainer/environments/environment.service/registries.ts b/app/portainer/environments/environment.service/registries.ts
new file mode 100644
index 000000000..4062d79cd
--- /dev/null
+++ b/app/portainer/environments/environment.service/registries.ts
@@ -0,0 +1,70 @@
+import axios, { parseAxiosError } from '@/portainer/services/axios';
+
+import {
+  EnvironmentId,
+  TeamAccessPolicies,
+  UserAccessPolicies,
+} from '../types';
+
+import { buildUrl } from './utils';
+
+export type RegistryId = number;
+export interface Registry {
+  Id: RegistryId;
+}
+
+interface RegistryAccess {
+  UserAccessPolicies: UserAccessPolicies;
+  TeamAccessPolicies: TeamAccessPolicies;
+  Namespaces: string[];
+}
+
+export async function updateEnvironmentRegistryAccess(
+  id: EnvironmentId,
+  registryId: RegistryId,
+  access: RegistryAccess
+) {
+  try {
+    await axios.put<void>(buildRegistryUrl(id, registryId), access);
+  } catch (e) {
+    throw parseAxiosError(e as Error);
+  }
+}
+
+export async function getEnvironmentRegistries(
+  id: EnvironmentId,
+  namespace: string
+) {
+  try {
+    const { data } = await axios.get<Registry[]>(buildRegistryUrl(id), {
+      params: { namespace },
+    });
+    return data;
+  } catch (e) {
+    throw parseAxiosError(e as Error);
+  }
+}
+
+export async function getEnvironmentRegistry(
+  endpointId: EnvironmentId,
+  registryId: RegistryId
+) {
+  try {
+    const { data } = await axios.get<Registry>(
+      buildRegistryUrl(endpointId, registryId)
+    );
+    return data;
+  } catch (e) {
+    throw parseAxiosError(e as Error);
+  }
+}
+
+function buildRegistryUrl(id: EnvironmentId, registryId?: RegistryId) {
+  let url = `${buildUrl(id)}/registries`;
+
+  if (registryId) {
+    url += `/${registryId}`;
+  }
+
+  return url;
+}
diff --git a/app/portainer/environments/environment.service/utils.ts b/app/portainer/environments/environment.service/utils.ts
new file mode 100644
index 000000000..1ec5c03a3
--- /dev/null
+++ b/app/portainer/environments/environment.service/utils.ts
@@ -0,0 +1,36 @@
+import { EnvironmentId } from '../types';
+
+export function buildUrl(id?: EnvironmentId, action?: string) {
+  let baseUrl = 'endpoints';
+  if (id) {
+    baseUrl += `/${id}`;
+  }
+
+  if (action) {
+    baseUrl += `/${action}`;
+  }
+
+  return baseUrl;
+}
+
+export function arrayToJson<T>(arr?: Array<T>) {
+  if (!arr) {
+    return '';
+  }
+
+  return JSON.stringify(arr);
+}
+
+export function json2formData(json: Record<string, unknown>) {
+  const formData = new FormData();
+
+  Object.entries(json).forEach(([key, value]) => {
+    if (typeof value === 'undefined' || value === null) {
+      return;
+    }
+
+    formData.append(key, value as string);
+  });
+
+  return formData;
+}
diff --git a/app/portainer/environments/types.ts b/app/portainer/environments/types.ts
index 4be0fa67e..3ab553913 100644
--- a/app/portainer/environments/types.ts
+++ b/app/portainer/environments/types.ts
@@ -1,12 +1,118 @@
 export type EnvironmentId = number;
 
+export enum EnvironmentType {
+  // Docker represents an environment(endpoint) connected to a Docker environment(endpoint)
+  Docker = 1,
+  // AgentOnDocker represents an environment(endpoint) connected to a Portainer agent deployed on a Docker environment(endpoint)
+  AgentOnDocker,
+  // Azure represents an environment(endpoint) connected to an Azure environment(endpoint)
+  Azure,
+  // EdgeAgentOnDocker represents an environment(endpoint) connected to an Edge agent deployed on a Docker environment(endpoint)
+  EdgeAgentOnDocker,
+  // KubernetesLocal represents an environment(endpoint) connected to a local Kubernetes environment(endpoint)
+  KubernetesLocal,
+  // AgentOnKubernetes represents an environment(endpoint) connected to a Portainer agent deployed on a Kubernetes environment(endpoint)
+  AgentOnKubernetes,
+  // EdgeAgentOnKubernetes represents an environment(endpoint) connected to an Edge agent deployed on a Kubernetes environment(endpoint)
+  EdgeAgentOnKubernetes,
+}
+
+export type TagId = number;
+
+export interface Tag {
+  Id: TagId;
+  Name: string;
+}
+
 export enum EnvironmentStatus {
   Up = 1,
-  Down = 2,
+  Down,
+}
+
+export interface DockerSnapshot {
+  TotalCPU: number;
+  TotalMemory: number;
+  NodeCount: number;
+  ImageCount: number;
+  VolumeCount: number;
+  RunningContainerCount: number;
+  StoppedContainerCount: number;
+  HealthyContainerCount: number;
+  UnhealthyContainerCount: number;
+  Time: number;
+  StackCount: number;
+  ServiceCount: number;
+  Swarm: boolean;
+  DockerVersion: string;
+}
+
+export interface KubernetesSnapshot {
+  KubernetesVersion: string;
+  TotalCPU: number;
+  TotalMemory: number;
+  Time: number;
+  NodeCount: number;
+}
+
+export interface KubernetesSettings {
+  Snapshots: KubernetesSnapshot[];
 }
 
 export interface Environment {
   Id: EnvironmentId;
+  Type: EnvironmentType;
+  TagIds: TagId[];
+  GroupName: string;
+  EdgeID?: string;
+  EdgeCheckinInterval?: number;
+  LastCheckInDate?: number;
+  Name: string;
   Status: EnvironmentStatus;
-  PublicURL: string;
+  URL: string;
+  Snapshots: DockerSnapshot[];
+  Kubernetes: KubernetesSettings;
+  PublicURL?: string;
 }
+
+/**
+ * TS reference of endpoint_create.go#EndpointCreationType iota
+ */
+export enum EnvironmentCreationTypes {
+  LocalDockerEnvironment = 1,
+  AgentEnvironment,
+  AzureEnvironment,
+  EdgeAgentEnvironment,
+  LocalKubernetesEnvironment,
+}
+
+export type EnvironmentGroupId = number;
+
+export interface EnvironmentSettings {
+  // Whether non-administrator should be able to use bind mounts when creating containers
+  allowBindMountsForRegularUsers: boolean;
+  // Whether non-administrator should be able to use privileged mode when creating containers
+  allowPrivilegedModeForRegularUsers: boolean;
+  // Whether non-administrator should be able to browse volumes
+  allowVolumeBrowserForRegularUsers: boolean;
+  // Whether non-administrator should be able to use the host pid
+  allowHostNamespaceForRegularUsers: boolean;
+  // Whether non-administrator should be able to use device mapping
+  allowDeviceMappingForRegularUsers: boolean;
+  // Whether non-administrator should be able to manage stacks
+  allowStackManagementForRegularUsers: boolean;
+  // Whether non-administrator should be able to use container capabilities
+  allowContainerCapabilitiesForRegularUsers: boolean;
+  // Whether non-administrator should be able to use sysctl settings
+  allowSysctlSettingForRegularUsers: boolean;
+  // Whether host management features are enabled
+  enableHostManagementFeatures: boolean;
+}
+
+export type UserId = number;
+export type TeamId = number;
+export type RoleId = number;
+interface AccessPolicy {
+  RoleId: RoleId;
+}
+export type UserAccessPolicies = Record<UserId, AccessPolicy>; // map[UserID]AccessPolicy
+export type TeamAccessPolicies = Record<TeamId, AccessPolicy>;