Skip to content

Issue and rotate API keys

Every connection your platform makes into a customer's project rides on an API key, and a key is a credential like any other in production. Left alone, a long-lived key with broad scopes is exactly the thing a security review flags: too much access, no expiry, no record of when it was last rotated. The Project API lets you run a key's whole life from your backend so it never becomes that liability:

  • Issue it scoped to one integration, with an expiry.
  • Audit the keys on the project before each rotation cycle.
  • Replace its secret on a schedule, with no downtime.
  • Revoke it the moment it is no longer needed.

This guide follows one key from the day you stand up an integration against a customer's project, through the routine audit and rotation that keeps it fresh, to the day you retire it.

Required scopes

The API key used for these calls needs the keys.read and keys.write scopes.

Issue a key scoped to the integration

When you connect a new integration to a customer's project, give it a key that can reach only the parts of the API that integration touches, and put an expiry on it from the start. Set up the client once, then issue the key. A sync job that reads and writes tables needs the tables.read and tables.write scopes and nothing more, so that is all its key gets.

import { Client, Project, ID, ProjectKeyScopes } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const project = new Project(client);

const result = await project.createKey({
    keyId: ID.unique(),
    name: 'Tenant integration',
    scopes: [ProjectKeyScopes.TablesRead, ProjectKeyScopes.TablesWrite],
    expire: '2026-12-31T23:59:59.000+00:00'
});

The secret is returned once, on creation, and never again. Write it straight to your secrets manager, because there is no call that reads it back.

Audit the project's keys

Rotation begins with knowing what is out there. When a rotation window comes due, list the keys on the project to see them all and when each one expires.

const result = await project.listKeys();

For any key that is close to expiry, read it back to confirm exactly what it can do before you decide how to handle it.

const result = await project.getKey({
    keyId: '<KEY_ID>'
});

Update a key's scopes or expiry

Sometimes a key just needs a smaller scope set or a later expiry, not a new secret. Updating a key changes its name, scopes, and expiry while keeping the same secret, so reach for it when you are adjusting access for an integration that has not changed credentials.

const result = await project.updateKey({
    keyId: '<KEY_ID>',
    name: 'Updated Key',
    scopes: [ProjectKeyScopes.TablesRead, ProjectKeyScopes.TablesWrite, ProjectKeyScopes.UsersRead],
    expire: '2027-06-30T23:59:59.000+00:00'
});

Rotate a key's secret

Rotating the secret itself, the part a security policy usually requires on a fixed cadence, means retiring one key and standing up another without a gap in service:

  1. Issue a fresh key with the same scopes using the create call above.
  2. Point the integration at the new secret.
  3. Confirm its traffic has moved over.
  4. Delete the old key so the retired secret stops working.
await project.deleteKey({
    keyId: '<KEY_ID>'
});

The delete takes effect immediately, so only run it once you have confirmed nothing is still authenticating with the old secret. The same call revokes a key that has leaked, without waiting for its expiry.

Issue a short-lived ephemeral key

Not every job deserves a long-lived key. A support engineer running a one-off migration, or a script that needs an hour of access, can take an ephemeral key instead. It carries its scopes and a lifetime of up to an hour, then expires on its own with nothing for you to revoke afterward.

const result = await project.createEphemeralKey({
    scopes: [ProjectKeyScopes.TablesRead, ProjectKeyScopes.TablesWrite],
    duration: 600
});

Next steps

Running a key's whole life from code is what lets you treat customer credentials as managed infrastructure instead of secrets someone pasted once. The same Project API covers the rest of a project's life: