Appearance
API Gateway Routes
Section: 15 — API
Last Updated: 2026-06-01
Scope: YARP route table, cluster configuration, CAE type, auth
Overview
Microtec ERP uses YARP (Yet Another Reverse Proxy) within the Gateway.Yarp project (Platforms/Src/Gateway/Gateway.Yarp/) as the API gateway. The gateway is the only internet-facing entry point for all API traffic.
Route Table
Routes are defined in appsettings.json under the ReverseProxy.Routes section. The gateway matches requests by path prefix and forwards to the corresponding cluster.
| Route ID | Path Pattern | Cluster | Downstream Service | Local Dev Port |
|---|---|---|---|---|
erp-apis | /erp-apis/{**catch-all} | erp-cluster | AppsPortal.Apis | :2005 |
inventory-apis | /inventory-apis/{**catch-all} | inventory-cluster | Inventory.Apis | :2008 |
business-owners-apis | /business-owners-apis/{**catch-all} | business-owners-cluster | BusinessOwners.Apis | :2003 |
admin-portal-apis | /admin-portal-apis/{**catch-all} | admin-portal-cluster | BusinessOwners.AdminPortal | :2007 |
attachments-apis | /attachments-apis/api/{**catch-all} | attachments-cluster | Attachment.Apis | :2030 |
notifications-apis | /notifications-apis/{**catch-all} | notifications-cluster | Notification.Apis | :2031 |
integration-apis | /integration-apis/{**catch-all} | integration-cluster | Integration.Apis | :2033 |
workflow-apis | /workflow-apis/{**catch-all} | workflow-cluster | Workflows.Apis | :2035 |
hr-apis | /hr-apis/{**catch-all} | hr-cluster | Hr.Personnel.Apis | :2046 |
template-apis | /template-apis/{**catch-all} | template-cluster | Template.Blazor | :2036 |
No Separate Finance / Sales / Purchase Routes
Finance, Sales, and Purchase modules are all served by AppsPortal.Apis (erp-cluster). There are no separate gateway clusters or route entries for those modules.
Special Routes
In addition to the main routes above, several specific path prefixes have dedicated route entries that also point to the same clusters:
| Route ID | Path Pattern | Cluster |
|---|---|---|
business-owners-sidemenu | /business-owners-apis/SideMenu/{**catch-all} | business-owners-cluster |
business-owners-translation | /business-owners-apis/Translation/{**catch-all} | business-owners-cluster |
erp-hrgeneralsetting | /erp-apis/HrGeneralSetting/{**catch-all} | erp-cluster |
erp-sidemenu | /erp-apis/SideMenu/{**catch-all} | erp-cluster |
erp-translation | /erp-apis/Translation/{**catch-all} | erp-cluster |
erp-swagger | /erp-apis/swagger/{**catch-all} | erp-cluster |
inventory-sidemenu | /inventory-apis/SideMenu/{**catch-all} | inventory-cluster |
inventory-translation | /inventory-apis/Translation/{**catch-all} | inventory-cluster |
inventory-swagger | /inventory-apis/swagger/{**catch-all} | inventory-cluster |
attachments-swagger | /attachments-apis/swagger/{**catch-all} | attachments-cluster |
notifications-swagger | /notifications-apis/swagger/{**catch-all} | notifications-cluster |
integration-swagger | /integration-apis/swagger/{**catch-all} | integration-cluster |
YARP Configuration
The gateway is configured via appsettings.json using YARP's declarative configuration:
json
{
"ReverseProxy": {
"Routes": {
"erp-apis": {
"ClusterId": "erp-cluster",
"Match": {
"Path": "/erp-apis/{**catch-all}"
}
},
"inventory-apis": {
"ClusterId": "inventory-cluster",
"Match": {
"Path": "/inventory-apis/{**catch-all}"
}
},
"business-owners-apis": {
"ClusterId": "business-owners-cluster",
"Match": {
"Path": "/business-owners-apis/{**catch-all}"
}
},
"admin-portal-apis": {
"ClusterId": "admin-portal-cluster",
"Match": {
"Path": "/admin-portal-apis/{**catch-all}"
}
},
"attachments-apis": {
"ClusterId": "attachments-cluster",
"Match": {
"Path": "/attachments-apis/api/{**catch-all}"
}
},
"notifications-apis": {
"ClusterId": "notifications-cluster",
"Match": {
"Path": "/notifications-apis/{**catch-all}"
}
},
"integration-apis": {
"ClusterId": "integration-cluster",
"Match": {
"Path": "/integration-apis/{**catch-all}"
}
},
"workflow-apis": {
"ClusterId": "workflow-cluster",
"Match": {
"Path": "/workflow-apis/{**catch-all}"
}
},
"hr-apis": {
"ClusterId": "hr-cluster",
"Match": {
"Path": "/hr-apis/{**catch-all}"
}
},
"template-apis": {
"ClusterId": "template-cluster",
"Match": {
"Path": "/template-apis/{**catch-all}"
}
}
},
"Clusters": {
"erp-cluster": {
"Destinations": {
"erp-cluster/destination1": {
"Address": "https://mic-erp-be-appsportal.internal.<env>.azurecontainerapps.io/"
}
}
},
"inventory-cluster": {
"Destinations": {
"inventory-cluster/destination1": {
"Address": "https://mic-erp-be-inventory.internal.<env>.azurecontainerapps.io/"
}
}
},
"business-owners-cluster": {
"Destinations": {
"business-owners-cluster/destination1": {
"Address": "https://mic-erp-be-businessowners.internal.<env>.azurecontainerapps.io/"
}
}
},
"admin-portal-cluster": {
"Destinations": {
"admin-portal-cluster/destination1": {
"Address": "https://mic-erp-be-adminportal.internal.<env>.azurecontainerapps.io/"
}
}
},
"hr-cluster": {
"Destinations": {
"hr-cluster/destination1": {
"Address": "https://mic-erp-be-hr.internal.<env>.azurecontainerapps.io/"
}
}
},
"integration-cluster": {
"Destinations": {
"integration-cluster/destination1": {
"Address": "https://mic-erp-be-integration.internal.<env>.azurecontainerapps.io/"
}
}
},
"attachments-cluster": {
"Destinations": {
"attachments-cluster/destination1": {
"Address": "https://mic-erp-be-attachments.internal.<env>.azurecontainerapps.io/"
}
}
},
"notifications-cluster": {
"Destinations": {
"notifications-cluster/destination1": {
"Address": "https://mic-erp-be-notifications.internal.<env>.azurecontainerapps.io/"
}
}
},
"workflow-cluster": {
"Destinations": {
"workflow-cluster/destination1": {
"Address": "https://mic-erp-be-workflows.internal.<env>.azurecontainerapps.io/"
}
}
},
"template-cluster": {
"Destinations": {
"template-cluster/destination1": {
"Address": "https://mic-erp-be-template.internal.<env>.azurecontainerapps.io/"
}
}
}
}
}
}Replace <env> with the Container App environment suffix (e.g., dev, stage, prod).
Authentication Pipeline
The gateway validates the JWT signature using Keycloak's public key (fetched from the OIDC discovery endpoint at startup). It does not decode or interpret claims — claim-based authorization is enforced by each downstream service individually.
json
// appsettings.json (Gateway)
{
"Authentication": {
"Authority": "https://<keycloak-host>/realms/microtec",
"Audience": "account",
"ValidateAudience": true,
"ValidateIssuer": true
}
}Downstream services receive the original Authorization: Bearer <token> header unchanged.
Health Checks
The gateway exposes health endpoints:
| Endpoint | Auth | Purpose |
|---|---|---|
GET /health | No | Liveness / readiness probe |
json
// GET /health
{
"status": "Healthy",
"entries": {
"erp-cluster": { "status": "Healthy" },
"inventory-cluster": { "status": "Healthy" },
"business-owners-cluster": { "status": "Healthy" },
"admin-portal-cluster": { "status": "Healthy" },
"hr-cluster": { "status": "Healthy" },
"integration-cluster": { "status": "Healthy" },
"attachments-cluster": { "status": "Healthy" },
"notifications-cluster": { "status": "Healthy" },
"workflow-cluster": { "status": "Healthy" },
"template-cluster": { "status": "Healthy" }
}
}Rate Limiting
Rate limiting is enforced upstream by Azure Front Door WAF (default: 10,000 requests per 5 minutes per client IP). The Gateway itself does not apply an additional rate limiting layer.
Ocelot (Legacy Reference)
Earlier versions of the gateway used Ocelot. If you see Ocelot-style configuration files (with UpstreamPathTemplate / DownstreamPathTemplate), these are from the legacy gateway and should not be used for new service configurations.
Migration to YARP was completed; the current source is at Platforms/Src/Gateway/Gateway.Yarp/.
Troubleshooting
502 Bad Gateway from gateway
bash
# Check downstream service health via ACA internal name
curl -k https://mic-erp-be-hr.internal.dev.azurecontainerapps.io/health
# Check gateway logs
az containerapp logs show --name mic-erp-be-dev-gateway --resource-group mic-erp-be-dev-compute-rgRoute not matched (404 at gateway)
bash
# Verify the route is in YARP config
grep -r "hr-apis\|erp-apis" Gateway.Yarp/appsettings*.json
# Test path matching
curl -v https://gateway.microtec-test.com/erp-apis/Invoices \
-H "Authorization: Bearer <token>"