Blog/tutorials

# Implementing Variable API Route Costs with Unkey

Learn how to efficiently implement variable costs for API routes using Unkey, an open-source API developer platform.

Written by

Lê Sỹ Cường

Published on

Jan 22, 2025

We all know API endpoints aren't created equal; some are more expensive than others. They can be more expensive due to compute cycles, or they could be financially more expensive because you are using a different AI model. When selling access to an API based upon usage with endpoints that could be very expensive, you need to be able to charge credits based on what endpoint a user is requesting data from. Below is an example of two routes: a cheap route and a more expensive one.

## How to implement cost based API usage

Below is an oversimplified version of tracking usage based upon an API key this doesn't include:

- API key creation
- Setting the credit amount for a user
- Safe way to look up an API key to get the usage base.

However, the idea is the following:

1. User requests one of the endpoints
2. We retrieve the key from the header
3. We get their remaining credits. If they have zero, we return an error
4. We reduce the credit amount based on the endpoint used
5. We update our Redis storage with the new value
6. We finally return the data from our endpoint

    1import { serve } from '@hono/node-server'
    2import { Hono } from 'hono'
    3import { createClient } from 'redis'
    4import { createMiddleware } from 'hono/factory'
    5
    6const app = new Hono()
    7
    8
    9const client = createClient();
    10
    11const verifyKeyWithCost = (cost: number) => createMiddleware(async (c, next) => {
    12  const key = c.req.header('X-API-Key');
    13  if (!key) {
    14    return c.json({ error: "No API key provided" }, 401);
    15  }
    16
    17  const credits = await client.get(key);
    18  if (credits === null) {
    19    c.json({ error: "Internal server error" }, 500);
    20  }
    21
    22  const remainingCredits = parseInt(credits) - cost;
    23  if (remainingCredits < 0) {
    24    c.json({ error: "API key usage exceeded" }, 429);
    25  }
    26
    27  await client.set(key, remainingCredits.toString());
    28  await next();
    29});
    30
    31// Cheap endpoint (costs 1 credit)
    32app.get('/cheap-endpoint', verifyKeyWithCost(1), (c) => {
    33  return c.json({
    34    message: "Accessed cheap endpoint",
    35    cost: 1,
    36  });
    37});
    38
    39// Expensive endpoint (costs 5 credits)
    40app.get('/expensive-endpoint', verifyKeyWithCost(5), (c) => {
    41  return c.json({
    42    message: "Accessed expensive endpoint",
    43    cost: 5,
    44  });
    45});
    46
    47serve(app)

There are a lot of issues we need to consider with the implementation above:

- What happens if a user makes multiple requests? How can we ensure accuracy and speed?
- We don't have rate-limiting requests, so we need to implement them to ensure there isn't an abuse vector.
- What happens if a user gets a new key? How do we ensure everything is accurate?

## How to simplify your implementation using Unkey

Unkey is an open-source API developer platform that allows developers to simplify the implementation of scalable and secure APIs.

With Unkey, with just a few lines of code, you can protect your API; the key holds all the information, so there isn't a requirement to look at another system to find this information. API keys can be created programmatically or through our dashboard.

    1const { result, error } = await verifyKey({
    2  key,
    3  remaining: {
    4    cost,
    5  },
    6  apiId: "API_ID_FROM_UNKEY",
    7});

Below, we took the above implementation and removed the need for:

- Implementing API key creation (Our example doesn't show this)
- Setting the credit amount for a user (Our example doesn't show this)
- Safe way to look up an API key to get the usage base
- Redis

    1import { Hono } from "hono";
    2import { serve } from '@hono/node-server'
    3import { createMiddleware } from "hono/factory";
    4import { verifyKey } from "@unkey/api";
    5
    6const app = new Hono();
    7
    8type UnkeyResult = Awaited<ReturnType<typeof verifyKey>>["result"];
    9
    10declare module "hono" {
    11  interface ContextVariableMap {
    12    unkey: UnkeyResult;
    13  }
    14}
    15
    16// Middleware to verify API key with specified cost
    17const verifyKeyWithCost = (cost: number) => createMiddleware(async (c, next) => {
    18  const key = c.req.header("X-API-Key");
    19  if (!key) {
    20    return c.json({ error: "No API key provided" }, 401);
    21  }
    22
    23  const { result, error } = await verifyKey({
    24    key,
    25    remaining: {
    26      cost,
    27    },
    28    apiId: "API_ID_FROM_UNKEY",
    29  });
    30
    31  /**
    32  * Handle Unkey Errors
    33  * We have others but not important for this example
    34  */
    35  if (error) {
    36    switch (error.code) {
    37      case "TOO_MANY_REQUESTS":
    38        return c.json({ error: "Rate limit exceeded" }, 429);
    39      case "BAD_REQUEST":
    40        return c.json({ error: "Bad request" }, 400);
    41      case "INTERNAL_SERVER_ERROR":
    42        return c.json({ error: "Internal server error" }, 500);
    43      default:
    44        return c.json({ error: "Internal server error" }, 500);
    45    }
    46  }
    47  /** Handle Unkey Result if it's not valid such as
    48  * Ratelimited, disabled, expired or no remaining credits
    49  * There are other errors but they're not needed for this example
    50  **/
    51  if (!result.valid) {
    52    switch (result.code) {
    53      case "DISABLED":
    54        return c.json({ error: "API key is disabled" }, 401);
    55      case "USAGE_EXCEEDED":
    56        return c.json({ error: "API key usage exceeded" }, 429);
    57      case "NOT_FOUND":
    58        return c.json({ error: "API key not found" }, 404);
    59      default:
    60        return c.json({ error: "Internal server error" }, 500);
    61    }
    62  }
    63  /** Add verification result to context for this example to show how Unkey works
    64  * This can also be used in a route handler for additional business logic
    65  **/
    66  c.set("unkey", result);
    67  await next();
    68});
    69
    70// Cheap endpoint - costs 1 credit
    71app.get("/cheap-endpoint", verifyKeyWithCost(1), (c) => {
    72  return c.json({
    73    message: "Accessed cheap endpoint",
    74    cost: 1,
    75    verificationResult: c.get("unkey")
    76  });
    77});
    78
    79// Expensive endpoint - costs 5 credits
    80app.get("/expensive-endpoint", verifyKeyWithCost(5), (c) => {
    81  return c.json({
    82    message: "Accessed expensive endpoint",
    83    cost: 5,
    84    verificationResult: c.get("unkey")
    85  });
    86});
    87
    88serve(app)

As you can see we really simplified the code and removed additional lookups via databases, allowing your API to be performant and scalable. You can get started for free today!

Written by

Lê Sỹ Cường

Published on

Jan 22, 2025

Contents

- How to implement cost based API usage
- How to simplify your implementation using Unkey

Suggested

Vncod — Hành trình định nghĩa lại tiêu chuẩn vận hành Game

Mar 26, 2026

How to ratelimit public pages

Dec 11, 2025

Building a flow editor

Dec 02, 2025

## Sẵn sàng bứt phá doanh thu.
Bắt đầu ngay hôm nay.

Liên hệ với chúng tôi
Server Game

Hỗ trợ cài đặt trọn gói. Cam kết vận hành ổn định 24/7.

We value your privacy
This site uses cookies to improve your browsing experience, analyze site traffic, and show personalized content.