Skip to content

    Apigee

    API Design, Products and Developer Apps in Apigee

    Design an API with OpenAPI, version it with base paths, bundle it into API products, and onboard developers, apps and keys in Apigee, step by step.

    By Published 20 min read

    Share

    This lesson is part of Apigee API Management: Beginner to Advanced. Enroll for free, then sign in, to read it in full.

    What you'll learn: How to design an API before building it, turn an OpenAPI spec into an Apigee proxy, version it with base paths, bundle it into API products, and onboard developers with apps and keys. You will also see how the pieces fit together over the API lifecycle, from first spec to retirement.

    Most teams meet Apigee as a place to put policies: verify a key here, add a quota there. That view misses half of the platform. Apigee is also where you decide what you offer and to whom. A partner who pays for read-only access and an internal team with full access can call the same backend through the same proxy, and Apigee can still treat them differently. The mechanism is the API product, and the quality of your design decides whether that mechanism stays simple or turns into a pile of special cases.

    The core idea

    Think of a restaurant. The kitchen (your backend) cooks. The menu (your OpenAPI spec) says what can be ordered. A set menu, such as the lunch special, is a bundle of dishes at a fixed price (an API product). A diner books a table (a developer app) and gets a reservation code (the credential). The waiter (the proxy) checks the code, checks that it covers the dish being ordered, and carries the order to the kitchen.

    Four ideas to hold on to:

    • The API proxy is the implementation: it receives calls, applies policies and forwards them. You build it once per API version.
    • The API product is the offer: a named bundle of operations, with environments, quota and scopes attached. Apigee says it directly: keys are provisioned "not for APIs themselves, but for API products".
    • The developer app is the consumer's registration. Each app must be associated with at least one product.
    • The credential (a consumer key and secret) belongs to an app and is approved for one or more products.
    How a developer, app, credential, API product and proxy relateDeveloper to Developer app (registers); Developer app to Credential (holds); Credential to Apigee product (approved for); Apigee product to Apigee proxy (operations); Apigee proxy to Orders backend (target)INSIDE APIGEEDeveloper: partner engineerDeveloperpartner engineerDeveloper app: one per consumerDeveloper appone per consumerCredential: consumer key and secretCredentialconsumer key andsecretApigee product: orders-readApigee productorders-readApigee proxy: orders-v1Apigee proxyorders-v1Orders backend: internal serviceOrders backendinternal serviceregistersholdsapproved foroperationstarget

    How to read it: the product is the hinge. It points at proxies on one side (through operations) and at credentials on the other (through approval). Change a product and every app holding it changes behaviour, without touching a proxy.

    API-first: design the contract before the code

    API-first means the contract exists, and is reviewed, before anyone writes the proxy or the service. In practice the contract is an OpenAPI specification: a YAML or JSON file that lists every path, method, parameter, request body and response. Benefits that matter at a gateway:

    • Consumers, security reviewers and the backend team can argue about the contract while it is still cheap to change.
    • The same file generates documentation, test cases and, as shown next, the proxy itself.
    • The spec becomes the reference for what a product may expose.

    Here is a small spec for the orders API used throughout this lesson. It is deliberately short, but valid OpenAPI 3.0.

    openapi: 3.0.3
    info:
      title: Orders API
      version: 1.0.0
      description: Read and create customer orders.
    servers:
      - url: https://orders-backend.example.internal   # the real backend, becomes the proxy target
    paths:
      /orders:
        get:
          operationId: listOrders
          summary: List orders, newest first
          parameters:
            - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 25 } }
            - { name: cursor, in: query, schema: { type: string }, description: Opaque cursor from the previous page }
            - { name: status, in: query, schema: { type: string, enum: [open, shipped, cancelled] } }
          responses:
            "200":
              description: A page of orders
              content:
                application/json:
                  schema: { $ref: "#/components/schemas/OrderPage" }
            "400": { $ref: "#/components/responses/Error" }
        post:
          operationId: createOrder
          summary: Create an order (idempotent when Idempotency-Key is sent)
          parameters:
            - { name: Idempotency-Key, in: header, required: true, schema: { type: string, maxLength: 64 } }
          requestBody:
            required: true
            content:
              application/json:
                schema: { $ref: "#/components/schemas/NewOrder" }
          responses:
            "201":
              description: Created
              content:
                application/json:
                  schema: { $ref: "#/components/schemas/Order" }
            "400": { $ref: "#/components/responses/Error" }
      /orders/{orderId}:
        get:
          operationId: getOrder
          summary: Fetch one order
          parameters:
            - { name: orderId, in: path, required: true, schema: { type: string } }
          responses:
            "200":
              description: The order
              content:
                application/json:
                  schema: { $ref: "#/components/schemas/Order" }
            "404": { $ref: "#/components/responses/Error" }
    components:
      responses:
        Error:
          description: Error in the standard error shape
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
      schemas:
        Order:
          type: object
          required: [id, status, totalCents]
          properties:
            id: { type: string }
            status: { type: string, enum: [open, shipped, cancelled] }
            totalCents: { type: integer }
        NewOrder:
          type: object
          required: [items]
          properties:
            items:
              type: array
              items: { type: object, required: [sku, quantity], properties: { sku: { type: string }, quantity: { type: integer, minimum: 1 } } }
        OrderPage:
          type: object
          properties:
            data: { type: array, items: { $ref: "#/components/schemas/Order" } }
            nextCursor: { type: string, nullable: true }
        Error:
          type: object
          required: [error]
          properties:
            error:
              type: object
              required: [code, message]
              properties:
                code: { type: string }
                message: { type: string }
                correlationId: { type: string }

    How a spec feeds proxy creation in Apigee

    Apigee can build a proxy from this file. In the Google Cloud console, open the API proxies page, choose Create, and under the OpenAPI spec template pick the reverse proxy option (the other choice builds a proxy with no target, covered below). Then upload the file. According to the Apigee tutorial, the wizard pre-fills the proxy details from the spec:

    Proxy field Where it comes from
    Name The spec title, with spaces replaced by dashes
    Base path The proxy name, lower-cased
    Description The spec description
    Target The server URL in the spec

    You review the details (the pre-filled base path is a starting point, so change it to something like /orders/v1), keep the operations you want, and choose environments to deploy to. Apigee then generates one conditional flow for each operation under paths. Only one conditional flow runs per request: the first whose condition is true. The next lesson explains flows in detail.

    A no-target proxy is the same wizard with no backend. It is useful as a mock: a proxy that answers from a policy instead of calling a service, so consumers can build against the contract before the backend exists. A request that never reaches a backend uses a null route (<RouteRule name="GoNowhere"/>, with the response created by a policy such as AssignMessage). The wizard only creates the no-target proxy; you add the policies that return the sample responses.

    A one-to-one relationship between your OpenAPI spec, your API proxy and your API product is a simple default: one spec, one proxy, one product, one place to look when something changes. Check the current Apigee guidance on it. The partner scenario below departs from it on purpose, with two products on one proxy, because the consumers need different operations and approval.

    Two further tools help with specs. Apigee API hub can register APIs with their specifications and versions, and it can lint specifications, so a style guide can be checked automatically. The integrated developer portal renders reference documentation from the same spec, which keeps the docs from drifting.

    REST design conventions that matter at a gateway

    A gateway sees every request as a method, a path, headers and a body. Design choices that look cosmetic in a spec become concrete once a product has to describe them. A broader treatment lives in API design for system designers; here are the points that bite at a gateway.

    Concern Convention Why it matters in Apigee
    Resources and verbs Plural nouns (/orders, /orders/{orderId}), verbs from HTTP (GET, POST, PUT, PATCH, DELETE) A product operation is a path plus optional methods. Clean nouns let you sell "read orders" by granting GET on /orders and /orders/* only
    Pagination Cursor or limit parameters, with a cap on page size Bounded responses protect the backend and the gateway's memory, and make quotas meaningful
    Filtering Query parameters (status=open) on the collection Filters stay in the query string, so one path covers many views and policies can still read them as flow variables
    Error contract One JSON shape for every error: code, message, request id Clients write one error handler. Gateway-raised errors (bad key, over quota) should match backend errors
    Idempotency Safe retries for PUT and DELETE by design; an Idempotency-Key header for POST The gateway forwards the header, but nothing in it deduplicates writes. The backend must honour the key
    Naming Consistent casing, no verbs in paths Predictable paths keep product wildcards simple

    Three consequences for Apigee specifically:

    1. Wildcards must match whole path segments. In a product, /* covers one level under the base path and /** covers every deeper level. An operation such as /orders/* means "any single order", and the design must put resource identifiers in their own segments for that to work.
    2. The error contract is a gateway job too. Failures raised by the proxy, such as an invalid key or an exceeded quota, are generated by Apigee, not your backend. A RaiseFault policy can return your standard shape:
    <RaiseFault name="RF-InvalidRequest">
      <IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
      <FaultResponse>
        <Set>
          <Payload contentType="application/json">{
      "error": { "code": "invalid_request", "message": "Pass a status of open, shipped or cancelled." }
    }</Payload>
          <StatusCode>400</StatusCode>
          <ReasonPhrase>Bad Request</ReasonPhrase>
        </Set>
      </FaultResponse>
    </RaiseFault>
    1. Idempotency is a backend promise. A gateway that retries a non-idempotent POST can charge a card twice. Decide where retries live (usually the client) and make POST /orders safe with a key stored by the backend.

    Versioning and how base paths implement it

    An API version is a contract with consumers. It is not the same as a proxy revision, which is a numbered snapshot of one proxy's configuration (a change you want to deploy is saved as a new revision). Revisions are how you ship fixes to a version. Versions are how you ship breaking changes.

    Strategy Example Strengths Weaknesses
    Path (URI) version /orders/v1/orders Visible, cacheable, easy to route and to put in a product Changing it moves URLs
    Header or media type Accept: application/vnd.example.orders.v2+json Clean URLs Harder to test with a browser, easier to forget in clients
    Query parameter /orders?version=2 Easy to try Easy to omit, muddles caching
    No version, evolve compatibly Only add optional fields Nothing to migrate Works only until a breaking change is unavoidable

    Path versioning fits Apigee well because of how routing works. A client call is a hostname, then a base path, then a resource path. The hostname comes from an environment group, and the base path selects the proxy. Base paths must be unique (the environments page states the rule per environment group, the configuration reference per environment), so two proxies cannot share one on the same hostname. That gives a simple pattern: deploy one proxy per major version, each with its own base path.

    flowchart LR C["Client call: api.example.com/orders/v2/orders"] --> H["Environment group routes by hostname"] H --> B{"Base path?"} B -->|"/orders/v1"| P1["Proxy orders-v1"] B -->|"/orders/v2"| P2["Proxy orders-v2"] P1 --> T1["Backend, contract v1"] P2 --> T2["Backend, contract v2"]

    Because each version is its own proxy, you can retire v1 without touching v2, give each version its own product, and run different policy sets per version. The cost is some duplication; shared flows (see the proxies lesson) keep the common parts in one place.

    Which strategy to use, and when to cut a new version, is a decision you can run as a flowchart.

    flowchart TD Q1{"Is the change breaking? Removes or renames a field, changes meaning, tightens validation"} -->|"No, additive only"| A1["Ship in the current version as a new proxy revision"] Q1 -->|"Yes"| Q2{"Can every consumer move in one release cycle? Internal callers only"} Q2 -->|"Yes"| A2["Coordinate the change, keep a short overlap, then delete the old behaviour"] Q2 -->|"No"| A3["Create a new major version: new proxy, new base path, new product"] A3 --> A4["Mark v1 deprecated, announce a sunset date, track usage per app until it reaches zero"]

    How to read it: most changes are additive and need no new version. A new major version is the expensive path, and the last step (watching which apps still call v1) is what makes retirement safe.

    API products: what you actually sell and share

    An API product is a named bundle of operations plus the rules that apply to them. Each operation names one proxy and a resource path, and can limit HTTP methods and carry its own quota. Around the operations a product carries:

    • Environments it is valid in. If the list is empty, all environments are allowed.
    • Approval type: keys are approved automatically, or each request waits for manual approval.
    • Quota: a limit (requests per interval) that applies to apps holding the product. A quota set on an operation takes precedence over the product-level quota.
    • OAuth scopes that apps may request.
    • Access level: public, private or internal only. Public products can be added to a developer portal. Apigee states there is no functional difference between private and internal only: the choice describes the intended audience.
    • Custom attributes, up to 18, which a proxy can read at runtime.

    Why products rather than proxies? A proxy answers "how does this API behave". A product answers "who may use which part of it, and how much". One proxy can sit behind many products: a read-only product, a read-write product, a high-quota premium product. Selling or sharing the product leaves the proxy untouched. Apigee's own examples include a free read-only tier and a premium tier with read and write access, built from the same resources.

    Here is the REST shape of a product with two operations, per the documented ApiProduct fields. Quota numbers are illustrative (assumed).

    {
      "name": "partner-orders-read",
      "displayName": "Partner Orders (read only)",
      "approvalType": "auto",
      "environments": ["prod"],
      "operationGroup": {
        "operationConfigs": [
          {
            "apiSource": "orders-v1",
            "operations": [{ "resource": "/orders", "methods": ["GET"] }],
            "quota": { "limit": "1000", "interval": "1", "timeUnit": "hour" }
          },
          {
            "apiSource": "orders-v1",
            "operations": [{ "resource": "/orders/*", "methods": ["GET"] }],
            "quota": { "limit": "5000", "interval": "1", "timeUnit": "hour" }
          }
        ]
      }
    }
    WARNING

    A product does not enforce anything by itself. A proxy must run a VerifyAPIKey or OAuth policy, and a Quota policy must be in the flow for quota values to apply. Always list the proxies and environments a product covers. Changes to products can also take about five minutes to take effect because Apigee caches product data.

    Developers, apps and credentials

    A developer is a registered identity, normally with an email address. A developer app belongs to a developer, is associated with at least one product, and receives credentials. The documented behaviour worth remembering:

    • Each app can hold multiple credentials, each with its own expiry date or none. That is how you rotate keys: generate a new credential, let the client switch, then revoke the old one. Old keys keep working until they expire or are revoked.
    • Each credential is linked to one or more products. The pair of credential and product has a status of approved or revoked. Manually approved products start as pending.
    • An app name cannot be changed after creation, and the optional callback URL is used by authorisation-code OAuth flows.
    • Apigee caches developer, app and token data for at least 180 seconds, so a revocation can take a few minutes to reach every message processor.

    The proxy has to check the credential. The policy is a few lines:

    <VerifyAPIKey name="VAK-VerifyKey">
      <APIKey ref="request.header.x-api-key"/>
    </VerifyAPIKey>

    Attach it to the proxy's PreFlow request. On success, Apigee sets flow variables named after the policy, for example verifyapikey.VAK-VerifyKey.app.name and verifyapikey.VAK-VerifyKey.apiproduct.name, which later policies and analytics can use. A key that does not match gives oauth.v2.InvalidApiKey, a missing key location gives oauth.v2.FailedToResolveAPIKey, and a valid key whose product does not cover the proxy and path gives oauth.v2.InvalidApiKeyForGivenResource. All three are HTTP 401. Match on the fault code, not the message text, which can change.

    The onboarding sequence

    This is what happens from sign-up to the first successful call, with a portal in front.

    sequenceDiagram participant D as Developer participant P as Developer portal participant A as Apigee control plane participant G as Apigee runtime (proxy) participant B as Orders backend D->>P: Sign up and browse API reference D->>P: Register app, pick product partner-orders-read P->>A: Create developer, app and credential A-->>P: Consumer key and secret (approved, or pending for manual products) P-->>D: Show credentials D->>G: GET /orders/v1/orders with x-api-key G->>G: VerifyAPIKey, check product covers path and method G->>G: Quota policy, counted per app G->>B: Forward request B-->>G: 200 response G-->>D: 200 response

    How to read it: the portal and the Apigee control plane hold registration data. The runtime only verifies and enforces. If the product is set to manual approval, the sequence pauses after "Create developer, app and credential" until an administrator approves.

    Scripting onboarding with the Management API

    The console is fine for a first try. For repeatable setup, the Apigee API at https://apigee.googleapis.com/v1 does the same. The commands below use gcloud for a token. Names, the organization and hostname are placeholders.

    export ORG=my-apigee-org
    export TOKEN=$(gcloud auth print-access-token)
    export API=https://apigee.googleapis.com/v1/organizations/$ORG
    
    # 1. Register a developer (email, firstName, lastName and userName are required)
    curl -s -X POST "$API/developers" \
      -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
      -d '{"email":"dev@partner.example","firstName":"Dana","lastName":"Partner","userName":"dana"}'
    
    # 2. Create the product (save the JSON shown above as product.json)
    curl -s -X POST "$API/apiproducts" \
      -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
      -d @product.json
    
    # 3. Create an app for that developer; Apigee generates a key for it
    curl -s -X POST "$API/developers/dev@partner.example/apps" \
      -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
      -d '{"name":"partner-checkout-app","apiProducts":["partner-orders-read"]}' \
      | tee app.json
    
    # 4. Read the consumer key from the credentials list and call the API
    KEY=$(jq -r '.credentials[0].consumerKey' app.json)
    curl -s -H "x-api-key: $KEY" "https://api.example.com/orders/v1/orders?limit=5"

    The proxy must already be deployed (product creation can fail if a referenced proxy does not exist in the environment) and api.example.com stands for a hostname on your environment group.

    Developer portals

    Developers need somewhere to find APIs, read documentation, register apps and get keys. Apigee's integrated portal is a managed answer. Per the documentation it generates reference documentation from OpenAPI specs, lets developers sign up and register apps themselves, stores apps and keys in Apigee, and supports audiences, a page editor and theming. Publishing an API to it is two steps: choose the product to expose, then attach the reference documentation, which can come from an OpenAPI document, an AsyncAPI document or a GraphQL schema. Apigee also documents Drupal-based portals as a customer-managed alternative; check the current docs for support in your edition before choosing one. Public products can be added to a portal; private and internal-only products are normally granted to apps by hand from the console or API.

    Monetization, briefly

    Apigee has a monetization feature in which rate plans attach to API products, so a product can be sold by consumption bands or similar models. It is an add-on with its own setup and constraints (for example, product names used with monetization must not have trailing spaces), so treat this lesson as the foundation and read the monetization documentation before designing pricing.

    The API lifecycle

    An API is not "done" when it ships. Treat it as a product with a life: it is designed, mocked, built, published, watched, then eventually deprecated and retired.

    stateDiagram-v2 [*] --> Design Design --> Mock: spec reviewed Mock --> Build: contract agreed Build --> Publish: tests pass, product created Publish --> Monitor: consumers onboarded Monitor --> Build: change request, new revision Monitor --> Deprecated: successor released Deprecated --> Retired: usage reaches zero or sunset date passes Retired --> [*]
    Stage What happens in Apigee
    Design Write and lint the OpenAPI spec; register it in API hub if you use it
    Mock A no-target proxy generated from the spec, with policies you add (such as AssignMessage) returning sample responses
    Build Proxy created or generated from the spec, policies added, revisions deployed to test
    Publish API product created, proxy deployed to production, product published on the portal
    Monitor Analytics and debugging per app and product (covered in the fault handling and observability lesson)
    Deprecate Set a custom attribute on the product that proxies read and use to flag it; return a Sunset header (RFC 8594) with the date; stop approving new keys
    Retire Undeploy the proxy, revoke the credentials, remove the product from the portal

    Custom attributes hold extra information on a product. Using one to flag deprecation, which a proxy reads at runtime to add the header or raise a fault, is this lesson's suggested design, not a documented feature, so test it in your own environment.

    Three use cases

    Public partner API Internal platform API Mobile app API
    Consumers External companies, known by contract Other teams in the company The company's own app, unknown devices
    Product design One product per tier (for example read-only, read-write) One product per domain or team One product for the app, narrow operations
    Access and approval Manual approval, quota per tier Auto approval, higher quota, internal-only access level Auto approval, tight quota
    Credential API key to start, OAuth for sensitive data OAuth client credentials or key OAuth with user tokens; never rely on a key hidden in the app
    Versioning Path versions, long overlap (months) Path versions, short overlap, coordinated Path versions, long overlap, because old app releases stay installed
    Portal Yes, public reference docs Often yes, inside the company Not needed

    Mobile is worth a second look. A key compiled into an app can be extracted by anyone who has the app, so the key only identifies the app and does not authenticate a user. User-level authorisation needs tokens, which the security lesson covers.

    Decision guide

    • One product or several for the same proxy? Several, when different consumers need different operations, quotas or approval. One, when everyone gets the same offer.
    • New proxy or new revision? A revision for compatible changes, a new proxy and base path for breaking ones.
    • Public, private or internal-only? Public for products you want on a portal. Private or internal-only describes who the product is meant for, and apps get it by being added manually.
    • Auto or manual approval? Manual wherever a human should know who is getting access, such as partners and anything with sensitive data.
    • Quota on the product or in the proxy? Put the number on the product so that it travels with the offer, and make sure a Quota policy in the proxy applies it.

    In practice: opening a partner Orders API

    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 retailer has an internal orders service used by its web shop. Three logistics partners now want to read order status and create orders. Today each partner gets a VPN account and direct access to the service, with no per-partner limits, and one partner's batch job slowed the shop last quarter (assumed). The team has four weeks (assumed) to offer a proper partner API, and must be able to turn off a single partner without affecting others.

    Starting architecture

    Starting point: partners reach the orders service directlyLogistics partner A to VPN; Logistics partner B to VPN; Logistics partner C to VPN; VPN to Orders service (no limits); Orders service to Orders databaseLogistics partner ALogisticspartner ALogistics partner BLogisticspartner BLogistics partner CLogisticspartner CVPN: one shared routeVPNone shared routeOrders service: also serves the web shopOrders servicealso serves theweb shopOrders databaseOrdersdatabaseno limits

    How to read it: every partner shares one route and one set of limits. Nothing identifies which partner caused load.

    What changes

    1. Write the contract. Create the OpenAPI spec for list, get and create orders, with the cursor pagination, the standard error shape and a required Idempotency-Key on POST. Review it with the three partners.
    2. Mock first. Generate a no-target proxy from the spec and add AssignMessage policies that return sample responses, so partners can start integration before the backend is ready.
    3. Build the real proxy. Generate a reverse proxy from the same spec with base path /orders/v1 and the orders service as target. Add VerifyAPIKey and a Quota policy in the PreFlow. Deploy to a test environment, then to production.
    4. Design two products. partner-orders-read (GET operations, 1,000 requests per hour on the list, assumed) and partner-orders-write (adds POST, lower quota, manual approval). Both point at the same proxy.
    5. Onboard. Publish both products on the integrated portal. Each partner registers a developer and an app, asks for the read product, and the team approves the write product by hand after contract checks.
    6. Cut over and close the VPN route once partner traffic is proven through the gateway.
    Target: partners call one governed proxy, each through its own app and keyPartner A app to Apigee proxy (HTTPS); Partner B app to Apigee proxy; Partner C app to Apigee proxy; Two API products to Apigee proxy (rules apply); Apigee proxy to Orders service (target); Orders service to Orders databasePartner A app: own key, read and writePartner A appown key, read andwritePartner B app: own key, read onlyPartner B appown key, readonlyPartner C app: own key, read onlyPartner C appown key, readonlyApigee proxy: orders-v1: key, quotaApigee proxyorders-v1: key,quotaTwo API products: read and writeTwo APIproductsread and writeOrders serviceOrders serviceOrders databaseOrdersdatabaseHTTPSrules applytarget

    Result

    What the learner should see: each partner has an app and a key of its own, so analytics and quota counters are separated per app. Revoking one app's credential stops that partner and nobody else. A partner that exceeds its quota gets a 429-style error in the standard error shape (the exact status and fault come from the Quota policy configuration), and the orders service sees only traffic that passed the gateway. When the team later needs a breaking change, a /orders/v2 proxy and new products run beside v1 while v1 is marked deprecated.

    Architect's notes

    • Start with the product, not the proxy. Ask "what will we offer and to whom" before "what paths exist". Products are cheap to add; renaming a published product breaks consumers and docs.
    • Keep products coarse enough to explain. Ten products with subtle differences confuse developers and the support desk. Three tiers read clearly.
    • A product is an enforcement point only with policies behind it. Review every proxy for the presence of VerifyAPIKey or OAuth. A missing policy means open access, and no error will tell you.
    • Plan for propagation delay. Product and credential changes can take minutes. Do not rely on a revoke being instant during an incident; add a gateway-side deny for emergencies.
    • Version for the slowest consumer. Mobile apps in the field and partner release cycles set your overlap period, not your own sprint calendar.
    • Cost. Every extra major version is another proxy to patch and test. Budget a sunset date when the version launches.
    • When not to use products. For a purely internal call between two services in one trust zone, a product and key per caller may be heavier than the problem; a mesh or platform identity may be enough (see API gateway and service mesh).

    Common mistakes

    • Creating a product with no proxies. An unscoped product is a security risk and hard to reason about. Always list the proxies and operations.
    • Assuming a product quota is enforced. Product quota values do nothing until a Quota policy runs in the proxy. Add the policy and test with a low limit.
    • Treating revisions as versions. A revision is a deployment snapshot. Breaking the contract inside a revision surprises every consumer. Cut a new base path for breaking changes.
    • One shared key for all consumers. You lose per-consumer limits, analytics and the ability to revoke one party. Issue one app per consumer.
    • Hiding a key in a mobile app and calling it security. Treat the key as an identifier and add OAuth user tokens.
    • Changing a spec without regenerating or reviewing the proxy. The proxy and the published docs drift apart. Review spec changes as part of the proxy change and automate the check in CI.
    • Matching error text in clients. Fault messages can change. Clients should switch on error codes in your own contract.
    • Forgetting the portal audience. A product marked private may not appear to developers who expect it. Check access level and portal audiences together.

    Key takeaways

    • A proxy is the implementation; a product is the offer; an app is the consumer; a credential links an app to products.
    • Design first with OpenAPI: Apigee can generate a reverse proxy or a no-target proxy (a base for a mock) from the spec, with one conditional flow per operation.
    • Keep one spec and one proxy per API version, with one product per offer: a simple one-to-one mapping is the default, and several products on one proxy is fine when offers differ.
    • Implement versions as base paths, one proxy per major version, and keep revisions for compatible changes.
    • A product bundles operations (proxy, path, methods, quota), environments, approval, scopes and attributes, but enforcement needs VerifyAPIKey or OAuth and Quota policies.
    • Use one app per consumer and rotate keys by adding a second credential before revoking the first.
    • Plan deprecation from day one: attribute, Sunset header, per-app usage tracking, then retirement.
    • Expect cache delays of minutes for product and credential changes.

    Practice exercises

    Starter (about 30 min): Write an OpenAPI spec for a small API of your choice (three paths, one with a path parameter, a standard error schema). In the Apigee console, generate a reverse proxy from it against the public mock target at https://mocktarget.apigee.net (edit the server URL first), deploy it, and call it with curl. Note how many conditional flows were generated and why.

    Intermediate (about 2 h): Create two products for one proxy: a read-only product and a read-write product with different quotas. Register one developer and two apps, one per product. Add VerifyAPIKey and Quota policies to the proxy. Prove with curl that the read-only app gets a 401 on POST, and that exceeding the quota (set it very low) returns an error. Script the onboarding with the Management API calls from this lesson.

    Advanced (half a day): Design a versioning and deprecation plan for an API with 20 consumers. Produce the v2 spec, a /v2 proxy and new products. Implement a proxy flow that reads a custom attribute on the v1 product and adds a Sunset header to every v1 response. Write the runbook for retirement, including how you will measure remaining usage per app and how you will revoke credentials. Add a mock proxy for v2 so consumers can start early.

    Official documentation

    Enroll to keep reading

    Lesson 3 of 11 · Apigee API Management: Beginner to Advanced

    Design an API with OpenAPI, version it with base paths, bundle it into API products, and onboard developers, apps and keys in Apigee, step by step.

    In the rest of this lesson

    • Versioning and how base paths implement it
    • API products: what you actually sell and share
    • Developer portals
    • Monetization, briefly
    • The API lifecycle
    • Three use cases

    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.

    Ask about this article

    Get answers grounded in this post. AI-generated — based on this article, and may be imperfect.

    Scaled AI Weekly

    Enjoyed this? Get more like it every Monday.

    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.

    Join engineers building production AI systems

    Comments