From Terra Incognita to API Incognita: What Captain Cook Teaches Us About API Discovery

 

 

When I was young, my parents often took me on UK seaside holidays, including a trip to Whitby,  which was known for its kippers (smoked herring), jet (a semi-precious stone), and the then vibrant fishing industry. Whitby was also where Captain Cook, a famous British explorer, learned seamanship.

During the 1760s, Cook surveyed Newfoundland and Labrador's coasts, creating precise charts of harbors, anchorages, and dangerous waters, such as Trinity Bay and the Grand Banks. His work, crucial for fishing and navigation, demonstrated exceptional skill and established his reputation, leading to his Pacific expeditions. Cook's charts, featuring detailed observations and navigational hazards, were so accurate they remained in use well into the 20th century.

This made me think about how cartography and API Discovery are very similar. API discovery is about mapping and finding unknown and undocumented APIs. When you know which APIs you're actually putting out there, you're much less likely to run into problems that could sink your application rollout like a ship that runs aground, or leaves you dead in the water like a vessel that's lost both mast and rudder in stormy seas.

This inspired me to show F5’s process for finding cloud SaaS APIs with a simple REST API. I honored Captain Cook and his Newfoundland trip by using this.

Roughly speaking...this is my architecture.

 

 

 

I thought about a simple rest API running in AWS. I call it the “Cook API”.  

An overview of the API interface is as follows.

 

"title": "Captain Cook's Newfoundland Mapping API",

        "description": "Imaginary Rest API for testing API Discovery",

        "available_endpoints": [

            {"path": "/api/charts", "methods": ["GET", "POST"], "description": "Access and create mapping charts"},

            {"path": "/api/charts/<chart_id>", "methods": ["GET"], "description": "Get details of a specific chart"},

            {"path": "/api/hazards", "methods": ["GET"], "description": "Get current navigation hazards"},

            {"path": "/api/journal", "methods": ["GET"], "description": "Access Captain Cook's expedition journal"},

            {"path": "/api/vessels", "methods": ["GET"], "description": "Get information about expedition vessels"},

            {"path": "/api/vessels/<vessel_id>", "methods": ["GET"], "description": "Get status of a specific vessel"},

            {"path": "/api/resources", "methods": ["GET", "POST"], "description": "Manage expedition resources"}

        ],

 

I set up a container running on a server in AWS that hosts my REST API.

To test my API, I create a partial swagger file that represents only a subset of the APIs that the container is advertising.

 

openapi: 3.0.0
info:
  title: Captain Cook's Newfoundland Mapping API (Simplified)
  description: >
    A simplified version of the API. This documentation shows only the Charts and Vessels endpoints.
  version: 1.0.0
  contact:
    name: API Support
    email: support@example.com
  license:
    name: MIT
    url: https://opensource.org/licenses/MIT

servers:
  - url: http://localhost:1234
    description: Development server
  - url: http://your-instance-ip
    description: Instance

tags:
  - name: Charts
    description: Mapping charts created by Captain Cook
  - name: Vessels
    description: Information about expedition vessels

paths:
  /:
    get:
      summary: API welcome page and endpoint documentation
      description: Provides an overview of the available endpoints and API capabilities
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                type: object
                properties:
                  title:
                    type: string
                    example: "Captain Cook's Newfoundland Mapping API"
                  description:
                    type: string
                  available_endpoints:
                    type: array
                    items:
                      type: object
                      properties:
                        path:
                          type: string
                        methods:
                          type: array
                          items:
                            type: string
                        description:
                          type: string

  /api/charts:
    get:
      summary: Get all mapping charts
      description: Retrieves a list of all mapping charts created during the Newfoundland expedition
      tags:
        - Charts
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: "success"
                  data:
                    type: object
                    additionalProperties:
                      $ref: '#/components/schemas/Chart'
    post:
      summary: Create a new chart
      description: Adds a new mapping chart to the collection
      tags:
        - Charts
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ChartInput'
      responses:
        '201':
          description: Chart created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: "success"
                  message:
                    type: string
                    example: "Chart added"
                  id:
                    type: string
                    example: "6"
        '400':
          description: Invalid input
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /api/charts/{chartId}:
    get:
      summary: Get a specific chart
      description: Retrieves a specific mapping chart by its ID
      tags:
        - Charts
      parameters:
        - name: chartId
          in: path
          required: true
          description: ID of the chart to retrieve
          schema:
            type: string
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: "success"
                  data:
                    $ref: '#/components/schemas/Chart'
        '404':
          description: Chart not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /api/vessels:
    get:
      summary: Get all expedition vessels
      description: Retrieves information about all vessels involved in the expedition
      tags:
        - Vessels
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: "success"
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/VesselBasic'

  /api/vessels/{vesselId}:
    get:
      summary: Get a specific vessel
      description: Retrieves detailed information about a specific vessel by its ID
      tags:
        - Vessels
      parameters:
        - name: vesselId
          in: path
          required: true
          description: ID of the vessel to retrieve
          schema:
            type: string
      responses:
        '200':
          description: Successful operation
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: "success"
                  data:
                    $ref: '#/components/schemas/VesselDetailed'
        '404':
          description: Vessel not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

components:
  schemas:
    Chart:
      type: object
      properties:
        name:
          type: string
          example: "Trinity Bay"
        completed:
          type: boolean
          example: true
        date:
          type: string
          format: date
          example: "1763-06-14"
        landmarks:
          type: integer
          example: 12
        risk_level:
          type: string
          enum: [Low, Medium, High]
          example: "Medium"

    ChartInput:
      type: object
      required:
        - name
      properties:
        name:
          type: string
          example: "St. Mary Bay"
        completed:
          type: boolean
          example: false
        date:
          type: string
          format: date
          example: "1767-05-20"
        landmarks:
          type: integer
          example: 7
        risk_level:
          type: string
          enum: [Low, Medium, High, Unknown]
          example: "Medium"

    VesselBasic:
      type: object
      properties:
        id:
          type: string
          example: "HMS_Grenville"
        type:
          type: string
          example: "Survey Sloop"
        crew:
          type: integer
          example: 18
        status:
          type: string
          enum: [Active, In-port, Damaged, Repairs]
          example: "Active"

    VesselDetailed:
      allOf:
        - $ref: '#/components/schemas/VesselBasic'
        - type: object
          properties:
            current_position:
              type: string
              example: "LAT: 48.2342°N, LONG: -53.4891°W"
            heading:
              type: string
              example: "Northeast"
            weather_conditions:
              type: string
              enum: [Favorable, Challenging, Dangerous]
              example: "Favorable"

    Error:
      type: object
      properties:
        status:
          type: string
          example: "error"
        message:
          type: string
          example: "Resource not found"

 

The process to set up API discovery in F5 Distributed Cloud is very simple.

  • I create an origin pool that points to my upstream REST API.
  • I then create a load balancer in distributed cloud and

o   Associate the origin pool with the load balancer

o   Enable API definition and import my partial swagger file as my API inventory. Some Screenshots below.

 

 

 

 

 

 

 

 

o   Enable API Discovery

  • Select Enable from Redirect Traffic
  • Run a shell script that tests my API.
  • Take a break or do something else for the API Discovery capabilities to populate the dashboard. The process of API Discovery to show up in the XC Security Dashboard can take several hours.

 

 

Results

 

Well, as predicted, API discovery has found that my Swagger file is only representing a subset of my APIs.

 

 

 

API discovery has found an additional 4 APIs that were not included in the swagger file. Distributed cloud describes these as “Shadow” APIs, or APIs that you may not have known about.

 

 

 

 

 

API Discovery has also discovered that sensitive data is being returned by couple of the APIs

 

 

 

What Now?

If this were a real-world situation, you would have to review what was found, paying special attention to APIs that may be returning sensitive data.

Each of what we call “shadow” APIs could pose a security risk, so you should review each of these APIs.

The good thing is that we are now using distributed cloud, and we can use distributed cloud to protect our APIs.

It is very easy to allow only those APIs that your project team might be using.

For the APIs that you are exposing through the platform, you can:-

  • Implement JWT Authentication if none exists and authentication is required.
  • Configure Rate Limiting
  • Add a WAF Policy 
  • Implement Bot Protection Policy
  • Continually log and monitor your API traffic.
  • Also, you should update your API inventory to include the entirety of the APIs that  the Application provides
  • You should only expose the APIs that are being used

All of these things are simple to set up in the F5 Distributed Cloud Application

 

Conclusion

You need effective API management and discovery. Detecting "Shadow" APIs is crucial to preventing sensitive data exposure. Much like Captain Cook charting unknown territories, the process of uncovering APIs previously hidden in the system reveals a need for precision and vigilance. Cook’s expeditions needed detailed maps and careful navigation to avoid hidden dangers. Modern API management needs tools that can accurately map and monitor every endpoint. By embracing this meticulous approach, we can not only safeguard sensitive data but also steer our digital operations toward a more secure and efficient future.

To quote a pirate who happens to be an API security expert and likes a Haiku.

Know yer API seas,
Map each endpoint 'fore ye sail—
Blind waters sink ships.

Published Jun 09, 2025
Version 1.0
No CommentsBe the first to comment