A credential stores the secret a model sends to the model provider, such as an API key. A credential holds:
- One secret, either
{"api_key": "..."}or{"basic_auth": "..."}. - An endpoint pattern, which lists the hosts the secret may be sent to.
- An owner, which is the role that created the credential.
One credential can serve many models. Each model uses exactly one credential. Any member of aidb_users can create credentials and models. No AIDB view or function returns a secret.
Creating a credential
Create a credential with aidb.create_credential(), then pass the credential name to aidb.create_model():
SELECT aidb.create_credential( name => 'openai_prod', credentials => '{"api_key": "sk-..."}'::JSONB, endpoint => 'api.openai.com' ); SELECT aidb.create_model( name => 'summarizer', provider => 'openai_completions', config => aidb.completions_config(model => 'gpt-4o-mini'), credential => 'openai_prod' );
endpoint is required and takes an endpoint pattern that lists the hosts the secret may be sent to. The secret is optional. To set it later, see Changing a credential.
A model that needs no secret, such as a local model, doesn't need a credential. A model created without credential uses the built-in credential none, which sends no secret. Every role can use none. The none credential can't be changed or deleted.
Endpoint patterns
Before a model sends a secret, AIDB checks the host the model calls against the credential's endpoint. The pattern is a comma-separated list of entries:
| Entry | Matches |
|---|---|
api.openai.com | Exactly that host. |
.openai.azure.com | That domain and any subdomain. |
192.168.1.5, 10.0.0.0/24, fd00::/8 | IP addresses in that range. Doesn't match host names. |
host:8443, [fd00::1]:8443 | That host on that port. Write an IPv6 address with a port in brackets. |
* | Any host. Creating a credential with the * entry raises a NOTICE. |
An entry without a port matches only the default port of the URL's scheme. *.example.com is rejected. Write .example.com instead.
If the host isn't allowed, the call fails before any request is sent:
ERROR: credential "openai_prod" allows endpoints "api.openai.com" but model "alice.elsewhere" calls host "example.com"
To check a pattern, or to see which host a model would call:
SELECT aidb.validate_endpoint_pattern('api.openai.com, .openai.azure.com'); SELECT aidb.endpoint_host('openai_completions', 'https://myorg.openai.azure.com/v1/chat/completions');
A model that sends a secret refuses "tls_config": {"insecure_skip_verify": true}. A private certificate authority (CA) set with tls_config.ca_path is allowed.
Listing credentials
To list credentials, query the aidb.credentials view:
SELECT name, owner, endpoint, shared, has_secret, can_use FROM aidb.credentials WHERE name = 'openai_prod';
name | owner | endpoint | shared | has_secret | can_use -------------+-------+----------------+--------+------------+--------- openai_prod | alice | api.openai.com | f | t | t (1 row)
Sharing a credential
Share a credential to give other roles access. A new credential is private. Only the credential owner can use the credential and the models that use the credential. Any other role that calls a model that uses the credential gets the following error:
ERROR: role lacks USAGE on credential "openai_prod" used by model "alice.summarizer"
To let every member of aidb_users use the credential, share the credential:
SELECT aidb.update_credential('openai_prod', share => true);
Pass share => true to aidb.create_credential() to share the credential at creation. Pass share => false to make the credential private again.
A credential is stored as a Postgres foreign server, so the Postgres privilege commands use SERVER syntax. You can run these commands manually for more fine-grained control:
GRANT USAGE ON FOREIGN SERVER openai_prod TO bob; -- GRANT just to "bob" instead of all "aidb_users"
A role that can use a credential can use every model that uses the credential.
Access checks
AIDB checks access to a credential for the role that runs the query, including a role set with SET ROLE. A SECURITY DEFINER function, and a pipeline with auto_processing set to Live, use their owner's access.
To see which credential a model uses, set client_min_messages to debug1 to show the message in your session, or set log_min_messages to debug1 to write it to the Postgres server log. The first call to the model in a session reports a line such as:
DEBUG: model "public.summarizer" uses credential "openai_prod"
Changing a credential
Use aidb.update_credential() to change a credential. Arguments you leave out stay unchanged. Changes take effect on the next call in every session. Only the owner of a credential, or a role that has the owner role's privileges, can change the credential. The same restriction applies to superusers.
-- Rotate the key SELECT aidb.update_credential('openai_prod', credentials => '{"api_key": "sk-new"}'::JSONB); -- Allow another host SELECT aidb.update_credential('openai_prod', endpoint => 'api.openai.com, .openai.azure.com'); -- Remove the secret SELECT aidb.update_credential('openai_prod', credentials => '{}'::JSONB);
A model whose credential has no secret fails with:
ERROR: credential "openai_prod" has no secret. Set one with aidb.update_credential('openai_prod', credentials => ...)Credentials can't be renamed. To use another name, create a new credential and recreate the models with the new credential.
Changing the owner
To hand a credential to another role, change the credential owner:
ALTER SERVER openai_prod OWNER TO carol;
Deleting a credential
Delete a credential with aidb.delete_credential(). The function deletes the credential and the credential's secret. If models use the credential, pass cascade => true to delete those models as well, including models that other roles created on a shared credential. aidb.delete_model() doesn't delete the model's credential.
SELECT aidb.delete_credential('openai_prod', cascade => true);
Passing a secret to aidb.create_model()
Pass a secret directly to aidb.create_model() instead of a credential name:
SELECT aidb.create_model( name => 'summarizer', provider => 'openai_completions', config => aidb.completions_config(model => 'gpt-4o-mini'), credentials => '{"api_key": "sk-..."}'::JSONB );
The call creates a credential named <schema>.<model>, for example public.summarizer. The credential is owned by you, is private, and has the host the model calls as the credential endpoint. Pass share => true to share the credential. If the host the model calls can't be determined, the endpoint is *.
- The call fails if the model or the credential already exists.
- To change the secret, call
aidb.create_model()again with the same arguments andreplace_credentials => true. - Quote the name in SQL:
GRANT USAGE ON FOREIGN SERVER "public.summarizer" TO bob;. - If
<schema>.<model>is longer than 63 bytes, the call fails. Create a credential and passcredentialinstead.
Note
A secret written as a literal in SQL can appear in pg_stat_activity, statement logs, and telemetry. In production, read the secret from an environment variable or Kubernetes Secret or pass the secret as a bind parameter from your client library.
Reading a secret from an environment variable or Kubernetes Secret
Instead of storing the secret in the database, a credential can read the secret from the Postgres server each time the model is used. Only superusers and members of the aidb_admins role can create such a credential:
SELECT aidb.create_credential( name => 'openai_env', credentials_env => 'AIDB_OPENAI_KEY', endpoint => 'api.openai.com', share => true );
Use one of the following arguments:
credentials_envnames an environment variable of the Postgres server. The name must start with the prefix set byaidb.env_var_allowed_prefix(AIDB_by default).credentials_k8s_secretnames a file, or a directory of files, from a mounted Kubernetes Secret. The path must start with the prefix set byaidb.k8s_secret_allowed_path_prefix.
A credential that reads a server secret works only while the credential owner is a superuser or a member of aidb_admins.
Limiting hosts for a server secret
The server operator can limit the hosts a server secret is sent to. Put an endpoint pattern in one of the following places:
| Secret | Host pattern |
|---|---|
Environment variable AIDB_OPENAI_KEY | Environment variable AIDB_OPENAI_KEY_HOST |
File api-key | File api-key_HOST in the same directory |
| Directory | File host in that directory |
When the host pattern is present, the model's host must match both the host pattern and the credential endpoint. Names and files that end in _HOST are never read as secrets.