Building Apigee API Proxies: Flows, Endpoints, Policies
Build an Apigee API proxy step by step: bundle structure, endpoints, flows, conditions, route rules, mediation policies and shared flows, with working XML.
What you'll learn: How an Apigee API proxy is put together, from the bundle on disk to the flows that run on each request. You will build a pass-through proxy, add a transformation, then add a conditional route, using real XML and curl commands.
A proxy is the thing you actually build in Apigee. Everything else in the platform (products, keys, quotas, analytics) hangs off it. The good news is that a proxy is not mysterious code: it is a folder of small XML files that say "when a request arrives, do these things in this order, then call that backend". Once you can read those files, you can debug any Apigee API.
The core idea
Picture a conveyor belt at an airport. A bag (the request) enters at one end, passes a series of stations (policies), is routed to the right plane (the backend), and the process runs again in reverse for the bag coming back (the response). Some stations are always there (the x-ray scanner). Others are used only for certain bags (the oversize counter). The belt is the flow; the stations are policies; the sign that says which plane to load is the route rule.
Apigee's proxy has exactly that shape, in two halves:
The ProxyEndpoint faces the client. It defines the base path clients call and the flows that run for the request and for the response going back.
The TargetEndpoint faces your backend. It defines where the backend lives and the flows that run just before calling it and just after it answers.
Policies are small XML components (verify a key, rename a header, convert XML to JSON). You declare each policy once in its own file and then attach it to a flow as a step. Behaviour is the order of the steps.
How to read it: the top row is the request going out, the bottom row is the response coming back. Each box is a place where you can attach policies. The route rule sits between the proxy request and the target request: it chooses which TargetEndpoint runs, or none.
Anatomy of a proxy bundle
A proxy is deployed as a bundle: a directory named apiproxy that you can zip and import. The Apigee configuration reference describes this layout.
flowchart TD
R["apiproxy/"] --> D["orders-facade.xml: descriptor, names the proxy"]
R --> PX["proxies/"]
R --> TG["targets/"]
R --> PO["policies/"]
R --> RS["resources/"]
PX --> PE["default.xml: ProxyEndpoint"]
TG --> TE["default.xml: TargetEndpoint"]
PO --> P1["AM-AddHeader.xml, EV-ParseId.xml, ..."]
RS --> R1["scripts, JAR files, XSLT files"]
What each part does:
Path
Contents
apiproxy/<name>.xml
The descriptor. A single <APIProxy name="..."> element. The name must be unique in the organization and may use only letters, digits, underscore and hyphen
proxies/default.xml
The ProxyEndpoint: base path, flows, route rules
targets/default.xml
The TargetEndpoint: the backend address and its flows
policies/*.xml
One file per policy, each with a unique name
resources/
Scripts (JavaScript), JAR files (Java callouts), XSLT files used by policies
A bundle can hold more than one ProxyEndpoint and several TargetEndpoints; the name default is just the convention for the first one. Because the whole proxy is text, it belongs in Git, which the CI/CD lesson builds on.
The ProxyEndpoint
The documented skeleton looks like this (the elements are shown empty so the structure is visible):
BasePath defaults to /. Clients call https://HOST/BASE_PATH/RESOURCE. A base path must be unique (the environments page states the rule per environment group, the configuration reference per environment), so two proxies cannot claim the same one on the same hostname. A wildcard segment such as /team/*/members is allowed, but the double wildcard /**/ is not.
The host name does not belong to the proxy in Apigee X. Host names are assigned to environment groups, and the environment group routes a request to an environment, where the base path then selects the proxy. If you inherit a bundle from Apigee Edge you may see VirtualHost elements; the reference sample for the ProxyEndpoint does not use them.
Properties is optional. For example, request.payload.parse.limit sets how large a payload the proxy will parse (the documented range is 10M to 30M, default 10M).
Fault rules handle errors. They are covered in the fault handling lesson.
A target endpoint uses one connection type: HTTPTargetConnection (the usual case), LocalTargetConnection (call another proxy in the same organization and environment), or ScriptTarget. With a null route (below) there is no TargetEndpoint at all.
Target servers and load balancing. Hard-coding a backend URL into the bundle means a different bundle for each environment. Instead, name a target server and let each environment define what that name means.
A target server is a named record (host, port, whether TLS is on) defined per environment; the same name can point to a test host in test and a production host in prod, while the bundle stays unchanged. The load balancer supports three algorithms: RoundRobin (the default), Weighted (each server needs a Weight) and LeastConnections. MaxFailures defaults to 0, which means Apigee never removes a server from rotation; the load balancing page says to set it above zero when you use a health monitor. One server can be marked as a fallback, used only when the others are unavailable. Path applies only when a LoadBalancer is present.
Flows: what runs, and in what order
Every segment of the pipeline (proxy request, target request, target response, proxy response) is made of three kinds of flow:
PreFlow always runs first. Use it for work that every call needs, such as verifying a credential.
Conditional flows run only when their Condition is true. Only one conditional flow runs per segment: Apigee tests them in order and runs the first whose condition is true, then skips the rest.
PostFlow always runs last in the segment. Use it for work that every call needs after the specific logic, such as adding a response header.
The full order for one request, as documented, is: proxy request PreFlow, proxy request conditional flows, proxy request PostFlow, then the same three for the target request, then the target response, then the proxy response, and finally the optional PostClientFlow. PostClientFlow runs after the response has been sent to the client, and only a few policy types can be attached there: the flows page lists FlowCallout, MessageLogging and ServiceCallout, while the configuration reference mentions only MessageLogging, so stick to MessageLogging for final logging.
Here is one request moving through that pipeline, with the policies we will build later.
sequenceDiagram
participant C as Client
participant PE as ProxyEndpoint
participant TE as TargetEndpoint
participant B as Backend
C->>PE: GET /legacy/v1/users/Dana
PE->>PE: PreFlow request: set correlation id
PE->>PE: Conditional flow GetGreeting: extract name
PE->>PE: RouteRule GreetingRoute picks target greeting
PE->>TE: forward request
TE->>TE: PreFlow request: name to query parameter
TE->>B: GET /user?user=Dana
B-->>TE: 200, Hello Dana
TE-->>PE: target response
PE->>PE: PostFlow response: echo correlation id
PE-->>C: 200 response
How to read it: all the Apigee-side work happens in the steps between client and backend. The proxy shapes the request before it leaves and the response before it returns.
A ProxyEndpoint flow section looks like this. A step is one policy name, optionally with its own condition.
<Flows><Flowname="GetGreeting"><Condition>(proxy.pathsuffix MatchesPath "/users/*") and (request.verb = "GET")</Condition><Request><Step><Name>EV-UserName</Name></Step></Request><Response/></Flow></Flows>
Conditions
A condition is an expression over flow variables: variable operator "value". Spaces around the operator are required. Combine tests with and, or and not, and group with parentheses. The documentation says Java operator precedence applies without listing the details, so add parentheses whenever you mix and with or.
Operator
Meaning
Example
= (or Equals)
Equal, case-sensitive
request.verb = "GET"
!=
Not equal, case-sensitive
request.verb != "OPTIONS"
:=
Equal, case-insensitive
request.header.accept := "application/json"
Matches or ~
Glob-style match with *, case-sensitive
request.header.user-agent Matches "*Mobile*"
JavaRegex or ~~
Java regular expression
proxy.pathsuffix JavaRegex "^/orders/[0-9]+quot;
MatchesPath or ~/
URI path match
proxy.pathsuffix MatchesPath "/orders/*"
StartsWith
Starts with, case-sensitive
proxy.pathsuffix StartsWith "/admin"
MatchesPath is the one to learn. In a path pattern, * matches one path segment and ** matches several levels. So /orders/* matches /orders/42 but not /orders/42/items, while /orders/** matches both. Matches is a plain glob over any string and is a common wrong choice for paths.
Other behaviour to know:
Missing values. Compare against null to test for an absent variable: request.header.x-correlation-id = null is true when the header is not set. The pattern operators (Matches, JavaRegex, MatchesPath) return false when the left side is null.
Conditions also work on steps. A <Condition> inside a <Step> decides whether that one policy runs, for example only when response.status.code = 503.
Use proxy.pathsuffix for resource routing. It is the part of the client URL after the base path.
Route rules
Once the proxy request flows finish, Apigee evaluates the RouteRules to decide where the request goes. Rules are checked top to bottom and the first match wins. A rule without a condition always matches, so the unconditional default rule must be last.
The first rule has no target. That is a null route: the request is not forwarded, and the proxy builds the response itself from policies. It is how you write a proxy that aggregates calls or returns a mock response. A RouteRule can also name a <URL> directly instead of a TargetEndpoint.
Flow variables
Flow variables are the proxy's shared memory. Apigee fills many of them (read-only facts about the message, the proxy and the caller) and you create your own. Policies read them in conditions and in {curly brace} templates inside XML. Some documented ones you will use constantly:
Variable
Meaning
Writable?
request.verb
HTTP method
No
request.header.NAME
A request header
Yes
request.queryparam.NAME
A request query parameter
Yes
request.uri
Base path plus the remaining path and query
No
proxy.basepath
The ProxyEndpoint base path
No
proxy.pathsuffix
The URL part after the base path
No
target.url
The backend URL, set in the target request flow
Yes
target.copy.pathsuffix
Whether the path suffix is forwarded to the target
Yes
response.status.code
Response status code
Yes
route.name
Name of the RouteRule that ran
No
messageid
A globally unique id for the request
No
client.ip
Address of the client (or of the system in front of Apigee)
No
Variables also have scope: proxy.pathsuffix is meaningful in the proxy request, target.url in the target request, and so on. A variable read outside its scope may be empty or unavailable. Custom variables are created by policies (<AssignVariable>, ExtractVariables, a ServiceCallout response) and last for the request.
The mediation policies you will use most
Mediation means changing a message in flight. These six policies cover most of it.
Policy
Job
Watch out for
AssignMessage
Set, add, copy or remove headers, query parameters, payload, status code, verb; set variables; build a new message
Without AssignTo it acts on the request in a request flow and on the response in a response flow
ExtractVariables
Pull values out of a path, header, query parameter or JSON/XML body into variables
The variable name is prefix.name; without a prefix it is just the name in braces
XMLToJSON and JSONToXML
Convert the body format
They apply only if the message Content-Type matches (application/xml for XMLToJSON, application/json for JSONToXML); otherwise the policy is skipped
ServiceCallout
Make an extra HTTP request from the proxy and keep the result in a variable
Set a sensible Timeout; the default is 55,000 ms
JavaScript
Run a script for logic no other policy covers
Keep to simple data work, set timeLimit, avoid system calls
JavaCallout
Run compiled Java from a JAR in resources/
Only when JavaScript or built-in policies are not enough
Shared flows and flow hooks
A shared flow is a reusable list of policy steps with no endpoint of its own. Proxies (and other shared flows) run it through a FlowCallout policy. Its bundle layout mirrors a proxy:
sharedflowbundle/
security-baseline.xml descriptor: <SharedFlowBundle name="security-baseline">
sharedflows/default.xml <SharedFlow name="default"> with <Step> elements
policies/ the policy files the steps name
resources/ scripts, JARs, XSLT
A bundle holds one shared flow, and the callout fails with flow.SharedFlowNotFound (HTTP 500) if the named shared flow does not exist or is not deployed. A flow hook attaches a shared flow to a fixed point for every proxy in an environment: PreProxyFlowHook (before the proxy endpoint runs), PreTargetFlowHook (before the target is called), PostTargetFlowHook (after the target answers) and PostProxyFlowHook (after the proxy endpoint, before the response is sent). Each hook accepts one shared flow. Because the effect is environment-wide, hooks suit organisation policy (a security baseline, response logging) and are a poor place for anything one team owns.
How to implement it: build three proxies' worth of behaviour in one
You will build one proxy called legacy-facade in four steps. The backend is Apigee's public mock target, https://mocktarget.apigee.net, which stands in for a legacy service. Its documented endpoints include /json and /xml (sample data), /echo (returns the request headers and body as JSON) and /user?user=NAME (a greeting).
Placeholders: ORG is your Apigee organization, ENV an environment such as test, and api.example.com stands for a host name on your environment group.
Import and deploy with the Apigee API. The import call takes the zipped bundle as a multipart file field; it creates the proxy, or a new revision if it exists. Deploying with override=true makes the new revision replace whichever revision is deployed in that environment.
export ORG=my-apigee-org ENV=testexport TOKEN=$(gcloud auth print-access-token)
export API=https://apigee.googleapis.com/v1/organizations/$ORG
(cd legacy-facade && zip -r ../legacy-facade.zip apiproxy)
# Optional: validate the bundle without importing it
curl -s -X POST "$API/apis?action=validate&name=legacy-facade" \
-H "Authorization: Bearer $TOKEN" -F "file=@legacy-facade.zip"# Import (the response includes the new revision number)
REV=$(curl -s -X POST "$API/apis?action=import&name=legacy-facade" \
-H "Authorization: Bearer $TOKEN" -F "file=@legacy-facade.zip" | jq -r '.revision')
# Deploy that revision to the environment
curl -s -X POST "$API/environments/$ENV/apis/legacy-facade/revisions/$REV/deployments?override=true" \
-H "Authorization: Bearer $TOKEN"
Try it (the first call returns the mock target's sample JSON unchanged; the second shows the request headers it received):
The path after /legacy/v1 is forwarded to the target, so /legacy/v1/json reaches https://mocktarget.apigee.net/json. If something goes wrong, open the Debug tool in the console, start a session on this proxy and repeat the call: it shows every flow, step, condition result and variable.
Step 2: add a transformation
The mock target returns XML at /xml. Convert it to JSON on the way back. Create apiproxy/policies/XMLToJSON-Body.xml, using the documented in-place pattern (source and output are both the response):
Attach it with a conditional flow in proxies/default.xml, placed before the route rules:
<Flows><Flowname="XmlAsJson"><Condition>(proxy.pathsuffix MatchesPath "/xml") and (request.verb = "GET")</Condition><Request/><Response><Step><Name>XMLToJSON-Body</Name></Step></Response></Flow></Flows>
Re-zip, import and deploy as before, then call https://api.example.com/legacy/v1/xml. The same four fields (city, first name, last name, state) now come back as JSON. The exact shape, such as whether they sit under a root key, depends on the policy options. If the response is still XML, check that the backend sets Content-Type: application/xml, because the policy is skipped otherwise.
The reverse works with JSONToXML on the request flow, with <Source>request</Source> and <OutputVariable>request</OutputVariable>. It needs Content-Type: application/json on the request.
Step 3: add a conditional route and a REST-to-legacy mapping
Suppose the legacy service wants /user?user=NAME, but you want clients to call /users/NAME. Do the mapping in three parts: extract the name, route to a second target, rewrite the request.
policies/EV-UserName.xml reads the name out of the path:
Test: curl https://api.example.com/legacy/v1/users/Dana should return Hello, Dana!, while /legacy/v1/json still takes the default route. The decision this step teaches is where each piece of logic belongs. Choosing the target is a route rule matter. Reshaping the request for one target belongs in that target's own flow. Pulling data out of the incoming call is a proxy request flow matter.
Step 4: use cases with real policies
The first use case (a REST facade over a legacy backend) is what you just built. Two more.
Use case 2: aggregation with ServiceCallout. One client call fans out to two backends and returns a combined result. This is a null route: the proxy answers itself. The two callouts hit the mock target's /json and /ip endpoints as stand-ins for a customer service and a network service.
policies/SC-Profile.xml (the second callout, SC-Network, is the same with another variable name and URL https://mocktarget.apigee.net/ip):
The flow runs the callouts in the request section and builds the response in the response section; the NoTarget route from the route-rule section sends nothing to a backend:
<Flowname="Summary"><Condition>(proxy.pathsuffix MatchesPath "/summary") and (request.verb = "GET")</Condition><Request><Step><Name>SC-Profile</Name></Step><Step><Name>EV-Profile</Name></Step><Step><Name>SC-Network</Name></Step><Step><Name>EV-Network</Name></Step></Request><Response><Step><Name>AM-BuildSummary</Name></Step></Response></Flow>
The two callouts run one after the other, so latency adds up; the Timeout bounds the damage. The routes page documents the null route but gives no example of building the response in it, so treat this AssignMessage as an outline and confirm the result with a Debug session before relying on it.
Use case 3: header normalisation and a correlation id. Every request should carry an X-Correlation-Id that follows it to the backend and back, using the client's value if present, otherwise a generated one. Two policies, each guarded by a step condition on whether the header exists:
Attach the first two in the ProxyEndpoint PreFlow with opposite step conditions, and the third in the PostFlow response. The complete ProxyEndpoint, with everything built so far:
<ProxyEndpointname="default"><PreFlowname="PreFlow"><Request><Step><Name>AM-CorrelationIdKeep</Name><Condition>request.header.x-correlation-id != null</Condition></Step><Step><Name>AM-CorrelationIdGenerate</Name><Condition>request.header.x-correlation-id = null</Condition></Step></Request><Response/></PreFlow><Flows><Flowname="XmlAsJson"><Condition>(proxy.pathsuffix MatchesPath "/xml") and (request.verb = "GET")</Condition><Request/><Response><Step><Name>XMLToJSON-Body</Name></Step></Response></Flow><Flowname="GetGreeting"><Condition>(proxy.pathsuffix MatchesPath "/users/*") and (request.verb = "GET")</Condition><Request><Step><Name>EV-UserName</Name></Step></Request><Response/></Flow><Flowname="Summary"><Condition>(proxy.pathsuffix MatchesPath "/summary") and (request.verb = "GET")</Condition><Request><Step><Name>SC-Profile</Name></Step><Step><Name>EV-Profile</Name></Step><Step><Name>SC-Network</Name></Step><Step><Name>EV-Network</Name></Step></Request><Response><Step><Name>AM-BuildSummary</Name></Step></Response></Flow></Flows><PostFlowname="PostFlow"><Request/><Response><Step><Name>AM-CorrelationIdResponse</Name></Step></Response></PostFlow><HTTPProxyConnection><BasePath>/legacy/v1</BasePath></HTTPProxyConnection><RouteRulename="NoTarget"><Condition>proxy.pathsuffix MatchesPath "/summary"</Condition></RouteRule><RouteRulename="GreetingRoute"><Condition>proxy.pathsuffix MatchesPath "/users/*"</Condition><TargetEndpoint>greeting</TargetEndpoint></RouteRule><RouteRulename="default"><TargetEndpoint>default</TargetEndpoint></RouteRule></ProxyEndpoint>
Test: curl -i https://api.example.com/legacy/v1/echo -H "X-Correlation-Id: abc-123" should echo abc-123 in both the body (the mock target reflects request headers) and the response header. Without the header, the value is a generated id.
Choosing between ServiceCallout, JavaScript, a shared flow and the built-ins
Reach for the simplest tool that does the job. The sequence of questions below is the decision.
flowchart TD
Q1{"Does a built-in policy already do it? AssignMessage, ExtractVariables, XMLToJSON, Quota..."} -->|"Yes"| A1["Use that policy"]
Q1 -->|"No"| Q2{"Do you need data from another HTTP service?"}
Q2 -->|"Yes"| Q3{"Is it another proxy in the same org and environment?"}
Q3 -->|"Yes"| A2["ServiceCallout with LocalTargetConnection"]
Q3 -->|"No"| A3["ServiceCallout to the URL, then ExtractVariables"]
Q2 -->|"No"| Q4{"Is the same set of steps needed by many proxies?"}
Q4 -->|"Yes"| A4["Shared flow called with FlowCallout, or a flow hook for every proxy"]
Q4 -->|"No"| Q5{"Small data massaging, a few lines?"}
Q5 -->|"Yes"| A5["JavaScript policy with a timeLimit"]
Q5 -->|"No"| A6["JavaCallout, or move the logic into a backend service"]
How to read it: logic that is heavy, stateful or domain-specific has no good home in a gateway. The last box is deliberate, because the right answer is often to move it into a service.
Need
Best fit
Cost
Add or rename a header, change a status code
AssignMessage
None
Combine two backend calls
ServiceCallout plus ExtractVariables
Extra latency per call, a new failure mode
Same security checks on every proxy
Shared flow plus flow hook
Environment-wide blast radius
Tricky string or JSON logic
JavaScript
CPU time, harder to test, a time limit
Existing compiled library
JavaCallout
JAR lifecycle to manage
In practice: fronting a legacy customer service
NOTE
A realistic scenario, not a case history. This is a composite example built from common production patterns. It does not describe any specific company, and every number in it is an illustrative assumption.
The situation
A bank's mobile team needs customer profile data held in a legacy service that returns XML, takes identifiers as query parameters (/customer?id=42) and has no correlation logging. Three mobile releases are in the field (assumed). The legacy team will not change the service this year, and support cannot trace a slow call across the app, the gateway and the service.
Starting architecture
How to read it: the app carries the legacy service's format quirks, and nothing links an app request to a log line.
What changes
Create a proxy with base path /customers/v1 and the legacy service as a target server named legacy-customers, defined per environment so test and production use different hosts.
In the ProxyEndpoint PreFlow, add the correlation id policies from this lesson (keep a client id, otherwise generate one).
Add a conditional flow GetCustomer for GET /customers/*. It extracts the id from the path, and a request rewrite in the target flow turns it into ?id=... with target.copy.pathsuffix set to false.
Add XMLToJSON to the proxy response flow of that conditional flow, so the apps receive JSON.
Add an /summary style flow that calls the customer and accounts services with ServiceCallout and returns one combined response, so the app makes one round trip.
Put the common steps (correlation id, standard headers) in a shared flow so later proxies reuse them.
Result
What the team should see: apps receive JSON with the same contract across releases, the legacy service is untouched, and a single correlation id appears in the app logs, the Apigee Debug session and the service logs, so support can follow one request end to end. Adding a second backend host is a target server change, not a code change. When something misbehaves, the Debug session shows which flow ran, which condition matched and which policy changed a variable.
Architect's notes
Keep the proxy thin. Routing, security, shaping and policy belong here; business rules belong in services. If a flow grows past a dozen steps or needs loops, move the logic out.
Prefer built-in policies over scripts. Declarative XML is easier to review and test than JavaScript, and it has no time limit to tune.
Name policies with a prefix and a verb (AM-, EV-, SC-, RF-) so a Debug trace is readable at a glance, and keep one concern per policy.
Budget latency. Every ServiceCallout adds a network hop and a failure mode. Set timeouts well under the client's own timeout, and decide what the proxy returns when an optional callout fails.
Externalise every environment difference into target servers and, where needed, key value maps, never into the bundle.
Flow hooks are a sharp tool. They run for every proxy in the environment, so test a change on a non-production environment first, and keep one owner.
Putting the default RouteRule first. An unconditional rule matches everything, so the conditional rules below it never run. Put default last.
Using Matches for paths.Matches is a string glob; use MatchesPath with * for one segment and ** for several.
Expecting every matching conditional flow to run. Only the first matching conditional flow per segment runs. Put the more specific condition first, or fold shared work into the PreFlow.
One giant PreFlow. Resource-specific logic in the PreFlow runs for every call. Use conditional flows per resource and keep the PreFlow for cross-cutting work.
Mixing and and or without parentheses. Add the parentheses; do not rely on precedence.
Converting a body whose Content-Type is wrong.XMLToJSON and JSONToXML are skipped silently if the header does not match. Check the header in Debug, and set it first if needed.
A ServiceCallout with no Response element. The proxy does not wait for a reply and cannot see a failure. Name a response variable and check it.
Leaving the callout timeout at the default. The default is 55 seconds. Set a short explicit Timeout.
Hard-coded backend URLs. The same bundle cannot be promoted between environments. Use target servers.
Calling a shared flow that is not deployed. The proxy fails at run time with a 500 (flow.SharedFlowNotFound). Deploy shared flows first and monitor them as part of the release.
Putting ordinary policies in PostClientFlow. Only a few policy types are allowed there; use it for MessageLogging.
Key takeaways
A proxy is a bundle of XML: a descriptor, a ProxyEndpoint, one or more TargetEndpoints, policy files and optional resources.
The ProxyEndpoint faces the client (base path, flows, route rules); the TargetEndpoint faces the backend (URL or target servers, flows).
Each segment has a PreFlow, conditional flows and a PostFlow, and only the first matching conditional flow runs.
Conditions test flow variables; use MatchesPath for paths and parentheses for mixed logic.
Route rules run top to bottom, first match wins, default last; a rule with no target is a null route.
AssignMessage, ExtractVariables, XMLToJSON, JSONToXML, ServiceCallout and JavaScript cover most mediation needs, in that order of preference.
Shared flows reuse steps via FlowCallout; flow hooks apply one to every proxy in an environment.
Build incrementally and use the Debug tool: pass-through first, then one change at a time.
Practice exercises
Starter (about 30 min): Build and deploy the pass-through legacy-facade proxy against the mock target. Call /json and /echo. Then add the XMLToJSON flow and confirm /xml returns JSON. Run a Debug session and note which flow and step ran.
Intermediate (about 2 h): Add the greeting facade (/users/NAME mapped to /user?user=NAME) with the conditional route, a second target and the rewrite policy. Add the correlation id policies and prove, using /echo, that a client-supplied id is preserved and a missing one is generated. Then change the proxy so /users/NAME with a verb other than GET returns a 405 using a RaiseFault policy.
Advanced (half a day): Create the /summary aggregation with two ServiceCallouts, then move the correlation id policies into a shared flow called through FlowCallout. Replace the hard-coded mock URL with a target server named mock-backend that you create in two environments pointing at different hosts, and promote the same zipped bundle through both. Write down what happens to the aggregation when the second callout times out, then decide and implement the behaviour you want.
Lesson 4 of 11 · Apigee API Management: Beginner to Advanced
Build an Apigee API proxy step by step: bundle structure, endpoints, flows, conditions, route rules, mediation policies and shared flows, with working XML.
In the rest of this lesson
Flow variables
The mediation policies you will use most
How to implement it: build three proxies' worth of behaviour in one
Choosing between ServiceCallout, JavaScript, a shared flow and the built-ins
In practice: fronting a legacy customer service
Architect's notes
Enrolling is free. It unlocks every lesson in Apigee API Management: Beginner to Advanced, tracks your progress across devices and keeps your quiz scores. The newsletter is optional.
Real architecture decisions, LLMOps patterns that survive production, and engineering leadership advice — from 12+ years of building at enterprise scale. Free. No spam. Unsubscribe anytime.
Get new articles by email
Free. We email a confirmation link first, and you can unsubscribe from any email.