Skip to main content
By default the client reads the site’s API key from the environment. You can keep it in your app’s database instead, encrypted, and change it by running one script, with no redeploy and no edit to your environment. That suits an app where the key is rotated now and then, or where the person who holds the key isn’t the person who deploys the app.

Encrypted at rest

AES-256-GCM from node:crypto, under an encryption key that never sits in the database.

Replaced in seconds

Store a new key from the CMS and the old one is gone from your app.

Same calls

site.blogPosts().listBlogs() and every other call work as before, signed as the key asks.

What you’ll build

The SDK has no database of its own, so this guide adds a few small files to your app. Each one is complete: copy it as shown. Using another database or an ORM? Only src/db.ts and the two queries in src/attract.ts change: see Other databases.

Before you start

The SDK is installed

From your own site, with any of its API keys: see Installation.

Your app has a database

The one your app already uses. This guide’s code is for PostgreSQL through pg; on another database, see Other databases. If your app doesn’t use pg yet, add it and its type declarations:
The guide’s script runs with tsx and asks its questions with @inquirer/prompts (which hides the key and secret as you type them). Add whichever your app doesn’t have yet as dev dependencies:

You have an API key from the site

In the site’s CMS, API Keys → New API Key shows three things, once. Keep the page open until you’ve stored them:
  • the API key, fsk_acme_…
  • its signing secret, fss_… (none for a token-only key)
  • its signing mode: the key’s Request signing setting, on its Security tab. You’ll pick from Token + signature on writes, Token + signature on every request or Token only

Set it up

Create the table

One row per site; most apps have one.
migrations/create_attract_api_keys.sql
Run it with your migration tool, or once by hand:
Terminal

Connect to the database

If your app already has a pg Pool, export it from src/db.ts. Otherwise:
src/db.ts

Create an encryption key

Generate 32 random bytes:
Terminal
Save the output as APP_ENCRYPTION_KEY wherever your app keeps its other secrets (a .env file that isn’t committed, or your host’s secret settings), never in the database:
.env
Then the helper that uses it:
src/crypto.ts

Store and read the key

storeKey() builds a client first, so a bad URL, key or secret throws before anything is written. The slug comes from the key itself (the acme in fsk_acme_…), and storing a key for a slug that already has a row replaces that row. siteFromDatabase() builds the client from the row, once.
src/attract.ts

Add the store-key script

It asks for what the CMS showed when the key was made, stores it, then makes one call to the site to prove the key works:
scripts/store-key.ts
Add it to package.json. --env-file loads .env; leave it out where your host already sets the variables:
package.json

Store the key

Terminal
Terminal

Call the site

Anywhere in your app:
src/posts.ts

Replace the key

When you have a new key for the site from its CMS, or its signing mode changes, run the script again with the new values:
Terminal
The site’s row is replaced, so the old key and secret are gone the moment the new ones are stored. A running server keeps the client it already built: restart it to pick up the new key.

Forget the key

To take the key out of your app, delete its row:
Terminal
Deleting the row doesn’t switch the key off: it keeps working on the site until you revoke it there too, in the CMS under API Keys.

Other databases

Only the connection and the two queries change. With Prisma, for example, model the same table:
prisma/schema.prisma
Then storeKey() writes with prisma.attractApiKey.upsert({ where: { slug }, create, update }) and siteFromDatabase() reads with prisma.attractApiKey.findMany(). Keep the encrypt() and decrypt() calls exactly where they are.

Test it

The encryption round trip needs no database:
tests/crypto.test.ts
Tests that call the site keep using fakeSite() from @fetchski/attract-cms-sdk/testing (Testing).
Losing APP_ENCRYPTION_KEY loses the stored key, so back it up with your other secrets. To change the encryption key, make a new API key in the CMS and store it under the new encryption key.