Skip to main content

# 🔐 Secure your LLM provider endpoint

## Video Turorial​

## Introduction​

To secure your LLM provider endpoint http://openai-demo.oto.tools:8080 for example, you could add API keys.

To do it so, add the API Keys plugin to your route :

Once added you should see your route as :

Then, we will try to send a request to our endpoint http://openai-demo.oto.tools:8080

    curl http://openai-demo.oto.tools:8080 \
      -H "Content-Type: application/json" \
      -d '{
        "messages": [
          {
            "role": "user",
            "content": "Hello how are you ?"
          }
        ]
      }'

The endpoint will return an error :

    {
      "Otoroshi-Error": "no apikey"
    }

That's good ! Because we just use our endpoint without any API Key and our request got rejected.

## Create new API Key​

Go to API Keys dashboard and create a new Key

Go to `Authorized On` section and select your route. `OpenAI Demo` in our example case.

Copy the Bearer token that may look like :

`otoapk_urjyypqbif8gr8wx_701cd2e99c0e2707e44bf6a7bb75518c90700a867d6c22f624020b22434cf7f9`

And then re-send a new request with our Bearer token into our request headers.

     curl http://openai-demo.oto.tools:8080 \
      -H "Content-Type: application/json" \
      -H "Authorization: Bearer otoapk_urjyypqbif8gr8wx_701cd2e99c0e2707e44bf6a7bb75518c90700a867d6c22f624020b22434cf7f9" \
      -d '{
        "messages": [
          {
            "role": "user",
            "content": "Hello how are you ?"
          }
        ]
      }'

Ta-da !

Here is our real response from the LLM with our authorized API key :

    {
      "id": "chatcmpl-SySPnMI3VzZKQZbvT42ko0H5YhrcIAVB",
      "object": "chat.completion",
      "created": 1743417992,
      "model": "gpt-4o-mini",
      "system_fingerprint": "fp-XfvVJ9gJ2viprCxZ1pyq508tukNbuz8u",
      "choices": [
        {
          "index": 0,
          "message": {
            "role": "assistant",
            "content": "Hello! I'm just a computer program, so I don't have feelings, but I'm here and ready to help you. How can I assist you today?"
          },
          "logprobs": null,
          "finish_reason": "stop"
        }
      ],
      "usage": {
        "prompt_tokens": 12,
        "completion_tokens": 31,
        "total_tokens": 43,
        "completion_tokens_details": {
          "reasoning_tokens": 0
        }
      }
    }

We can see in our API Key that the quotas for our API keys has been decreased due our request

### Full configuration​

Configuration for the API Key

    {
      "_loc": {
        "tenant": "default",
        "teams": [
          "default"
        ]
      },
      "clientId": "urjyypqbif8gr8wx",
      "clientSecret": "5icbe4o94dbwere7nupoecfmgr5kbo3cgpp0iv94gf1kxak04ocfw9tvu4f9nrmd",
      "clientName": "Hilario Flatley's api-key",
      "description": "",
      "authorizedGroup": null,
      "authorizedEntities": [
        "route_route_ca396e0a1-2bc6-41c5-a682-2a95c4ed0c24"
      ],
      "authorizations": [
        {
          "kind": "route",
          "id": "route_ca396e0a1-2bc6-41c5-a682-2a95c4ed0c24"
        }
      ],
      "enabled": true,
      "readOnly": false,
      "allowClientIdOnly": false,
      "throttlingQuota": 10000000,
      "dailyQuota": 10000000,
      "monthlyQuota": 10000000,
      "constrainedServicesOnly": false,
      "restrictions": {
        "enabled": false,
        "allowLast": true,
        "allowed": [],
        "forbidden": [],
        "notFound": []
      },
      "rotation": {
        "enabled": false,
        "rotationEvery": 744,
        "gracePeriod": 168,
        "nextSecret": null
      },
      "validUntil": null,
      "tags": [],
      "metadata": {
        "created_at": "2025-03-31T12:42:40.194+02:00"
      },
      "bearer": "otoapk_urjyypqbif8gr8wx_701cd2e99c0e2707e44bf6a7bb75518c90700a867d6c22f624020b22434cf7f9",
      "kind": "apim.otoroshi.io/ApiKey"
    }

Full route configuration with API Keys plugin :

    {
      "_loc": {
        "tenant": "default",
        "teams": [
          "default"
        ]
      },
      "id": "route_ca396e0a1-2bc6-41c5-a682-2a95c4ed0c24",
      "name": "OpenAI Demo",
      "description": "OpenAI Demo",
      "tags": [],
      "metadata": {},
      "enabled": true,
      "debug_flow": false,
      "export_reporting": false,
      "capture": false,
      "groups": [
        "default"
      ],
      "bound_listeners": [],
      "frontend": {
        "domains": [
          "openai-demo.oto.tools"
        ],
        "strip_path": true,
        "exact": false,
        "headers": {},
        "query": {},
        "methods": []
      },
      "backend": {
        "targets": [
          {
            "id": "target_1",
            "hostname": "request.otoroshi.io",
            "port": 443,
            "tls": true,
            "weight": 1,
            "predicate": {
              "type": "AlwaysMatch"
            },
            "protocol": "HTTP/1.1",
            "ip_address": null,
            "tls_config": {
              "certs": [],
              "trusted_certs": [],
              "enabled": false,
              "loose": false,
              "trust_all": false
            }
          }
        ],
        "root": "/",
        "rewrite": false,
        "load_balancing": {
          "type": "RoundRobin"
        },
        "client": {
          "retries": 1,
          "max_errors": 20,
          "retry_initial_delay": 50,
          "backoff_factor": 2,
          "call_timeout": 30000,
          "call_and_stream_timeout": 120000,
          "connection_timeout": 10000,
          "idle_timeout": 60000,
          "global_timeout": 30000,
          "sample_interval": 2000,
          "proxy": {},
          "custom_timeouts": [],
          "cache_connection_settings": {
            "enabled": false,
            "queue_size": 2048
          }
        },
        "health_check": {
          "enabled": false,
          "url": "",
          "timeout": 5000,
          "healthyStatuses": [],
          "unhealthyStatuses": []
        }
      },
      "backend_ref": null,
      "plugins": [
        {
          "enabled": true,
          "debug": false,
          "plugin": "cp:otoroshi.next.plugins.OverrideHost",
          "include": [],
          "exclude": [],
          "config": {},
          "bound_listeners": [],
          "plugin_index": {
            "transform_request": 0
          }
        },
        {
          "enabled": true,
          "debug": false,
          "plugin": "cp:otoroshi_plugins.com.cloud.apim.otoroshi.extensions.aigateway.plugins.OpenAiCompatProxy",
          "include": [],
          "exclude": [],
          "config": {
            "refs": [
              "provider_7e618efb-9c2c-42ed-870a-58b1ec3c6264"
            ]
          },
          "bound_listeners": [],
          "plugin_index": {}
        },
        {
          "enabled": true,
          "debug": false,
          "plugin": "cp:otoroshi.next.plugins.ApikeyCalls",
          "include": [],
          "exclude": [],
          "config": {
            "extractors": {
              "basic": {
                "enabled": true,
                "header_name": null,
                "query_name": null
              },
              "custom_headers": {
                "enabled": true,
                "client_id_header_name": null,
                "client_secret_header_name": null
              },
              "client_id": {
                "enabled": true,
                "header_name": null,
                "query_name": null
              },
              "jwt": {
                "enabled": true,
                "secret_signed": true,
                "keypair_signed": true,
                "include_request_attrs": false,
                "max_jwt_lifespan_sec": null,
                "header_name": null,
                "query_name": null,
                "cookie_name": null
              }
            },
            "routing": {
              "enabled": false
            },
            "validate": true,
            "mandatory": true,
            "pass_with_user": false,
            "wipe_backend_request": true,
            "update_quotas": true
          },
          "bound_listeners": [],
          "plugin_index": {
            "validate_access": 0,
            "transform_request": 1,
            "match_route": 0
          }
        }
      ],
      "kind": "proxy.otoroshi.io/Route"
    }