content_url, never a credential
You configure the store once. Every
s3:// skill reuses that one connection. Which users may see which skill is controlled separately by access groups, not by handing out keysOption A: AWS S3
Use this for a real cloud bucket shared across environments The walkthrough below uses a bucket namedforceai-skills in us-east-1 holding one object, pdf.md. Substitute your own names as you go
1
Create the bucket and upload a skill file
In the AWS console go to S3 -> Create bucket. Give it a name such as
forceai-skills and pick a region such as us-east-1. Leave “Block all public access” on; the gateway reaches objects with credentials, never public URLs. Upload your skill body as an object, for example pdf.md, so its key is forceai-skills/pdf.md2
Create a scoped IAM access key
This is the one value the ForceAI form cannot generate for you; it comes from AWS. In IAM -> Users -> Create user add a user such as
forceai-skills-loader with no console access, then attach an inline policy scoped to just this bucket:s3:GetObject is required so the gateway can load a body on demand. s3:PutObject is only needed if you will use the in-dashboard “Upload body” mode; drop it if you always upload objects yourself. Then under Security credentials -> Create access key copy the Access key ID (starts with AKIA) and the Secret access key. The secret is shown once, so copy it now3
Open the store panel in ForceAI
Sign in to the dashboard at
http://localhost:3000 (default admin / your LITELLM_MASTER_KEY). In the left navigation open Skills, then select the Model-agnostic skills tab. The store lives in the single row under the “Object store (S3-compatible)” heading4
Fill the store row field by field
The row is left to right: a provider dropdown, then four text boxes, then the save button. Set each one:
The region does not have a field in this row and defaults correctly for
us-east-1. For any other region use the API form, which takes region explicitly5
Save
Click Save store. The tag at the end of the row flips from
not configured to configured, and field 5 changes to show “secret set (leave blank to keep)”. The secret is encrypted at rest and is never returned again; on a later edit you can leave field 5 blank to keep the stored oneOption B: local minio
The stack ships a minio container on port9000 with its console on 9001, so you can prove the whole flow with no cloud account
1
Create the bucket in minio
Open the minio console at
http://localhost:9001 and sign in with the dev credentials forceai / forceai-minio-dev (or your MINIO_ROOT_USER / MINIO_ROOT_PASSWORD overrides). Go to Buckets -> Create Bucket and add skills2
Pick credentials
Reuse the root credentials above, or create a dedicated pair under Access Keys -> Create for least privilege
3
Enter the connection in ForceAI
On the Skills page Model-agnostic Skills tab set:
Click Save
Add a skill
With the store showingconfigured, click + Add skill on the same panel. A dialog opens with a mode toggle at the top and four fields below
1
Pick the source mode
Leave the toggle on Reference URL to point at an object that already exists in the bucket (this is the common case). Switch to Upload body only when you want to paste the
SKILL.md text and have ForceAI write it to the bucket for you; that mode needs the s3:PutObject permission from step 2 above2
Fill the fields
For the
forceai-skills/pdf.md object from the walkthrough:3
Save
Click OK. The skill appears in the table below with its Enabled switch already on. Leave it enabled
Verify a skill loads
Enable skills on a request and confirm the body is injected for that turn:spend_logs_metadata.forceai_skills. It records available, loaded, loaded_names, and tokens_loaded, so you can confirm the matching skill was fetched from the store and injected while the non-matching skills were never loaded
Configure the store by API
The dashboard form is a thin wrapper over one admin-only endpoint on the control plane. The secret is write-only: it is accepted here and never returned by the matchingGET
"provider": "minio" and add "endpoint_url": "http://minio:9000". On an update you may omit secret_access_key to keep the stored one
GET /forceai/skills/store returns the provider, bucket, endpoint, region, and a secret_set boolean only. It never returns the secret itself