Skip to main content

What makes a good API key?

Written by on .

engineering
api
security

A customer sent me a support email saying that they were having trouble with their API key.

The first version of Glama API keys were just a random UUIDs, and the particular API key was: 05e3d22f-640b-4257-9325-8c2a8144639f.

However, I couldn't find any information about the API key in our database, and with this being just a random UUID, there wasn't much to go on.

After a few back and forths, it turned out that they were referencing an API key for a deleted account. However, this interaction made me want to rethink our API key format.

What others are using?

The first thing I did was to look at the API keys of services that Glama is already using.

Here is a selection of API keys from various companies:

Obviously, the API keys were altered.

Anthropic API key:

sk-ant-api03-2VOgiuf5Vo2ZwrZhE7qN_Xm-VSY4c5Mh6EfHlCeERJaq2p1T-w28JzGKSMtgtYhy8GDKEBTeAx9EWanxbYN-w-EPAVhUQA

OpenAI API key:

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

Fun fact: you can identify OpenAI API keys by the way they contain "T3BlbkFJ" (OpenAI in Base64). This makes it easy to detect them in a secret scanner for preventing leaks in repos without false positives.

Groq API key:

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

Perplexity API key:

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

Stripe API key:

pk_test_cA1zEJhAFN42AC4Idt1PfMYeX1BD00SbkrsKFq53bestoib7MXKZXoru7XeD3ShOogRtltmz6fWcpiebgNzW9PicmFF4ZRMjFWo

From this exercise I learned that:

  • Many API keys have a prefix that indicates the type of key (e.g., sk- for secret keys in OpenAI, pk_ for public keys in Stripe, gsk_ for Groq, pplx- for Perplexity).
  • Some keys also include suffixes that may indicate the environment (e.g., test in Stripe's key).
  • Some keys are segmented by hyphens or underscores. Presumably, it's done not just for readability, but also used to encode data within the key that allows grouping of keys (e.g., api03 in Anthropic's key describes the API version).
  • Lengths vary significantly, but most are quite long (48+ characters).

Sentry API key

One interesting exception that caught my eye was the Sentry API key.

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

The key includes a Base64URL-encoded JSON object with the following structure:

sntrys_<encoded details>_<secret value>

Base64URL is a variant of Base64 encoding that is URL-safe and filename-safe. It uses '-' and '_' instead of '+' and '/' respectively, and omits padding characters. This makes it ideal for use in URLs and filenames without requiring additional encoding.

The payload is a JSON object with the following structure:

{
  "iat": 1729278581.081153,
  "url": "https://sentry.io",
  "region_url": "https://us.sentry.io",
  "org": "glama"
}

iat here is the date issued at (iat stands for "issued at").

The thing that I like about the Sentry's format is that the key itself encodes information that is useful for troubleshooting purposes.

Designing the perfect API key

Based on the above, here is my design for the API key format:

  • The key should have a prefix that describes the organization that owns the key.
  • The key should have a stable fragment that can be used as public key identifier.
  • The key itself should encode information that is useful for troubleshooting purposes (e.g., user ID).

Which, in case of Glama, translates to:

  • glama_ prefix
  • public key identifier
  • user ID
  • secret (UUID)

This translates to:

<prefix>_<public key identifier>.<encoded payload>

Here is an example:

glama_bf0629ac.eyJ1c2VyIjoienQwdjFqcWN4OSIsInNlY3JldCI6ImJmMDYyOWFjLWFkNDgtNGYzMS1iMTFkLWIyMTZmYzMwNzFhMSJ9

which can be decoded to:

{
  "user": "zt0v1jqcx9",
  "secret": "bf0629ac-ad48-4f31-b11d-b216fc3071a1"
}

This API key has the benefit that:

  • Can be identified as belonging to Glama.
  • Can be uniquely identified by the unique public identifier of the API key.
    • This would be the ID that identifies the key in the dashboard.
  • Can be used to identify the user by decoding the Base64URL payload.
  • Can be expanded to include more information over time (such as the API endpoint, like in the case of Sentry).

The reason for Base64URL encoding the key is that it allows us to expand the information that is encoded in the key without changing the format of the key itself.

On the server side, I decode the payload and use the information to identify the user.

Conclusion

The API key could be as simple as a random UUID, but adding a prefix and encoding the payload enables greater usability and troubleshooting.