API Providers are the foundation of HandsAI’s tool registry. Each provider represents an external API service (like GitHub, Resend, or Tavily) and contains authentication configuration, base URL, and a collection of tools.
What is a Provider?
A Provider is a logical grouping of API tools that share:
Base URL - The root endpoint (e.g., https://api.github.com)
Authentication - API keys, bearer tokens, or OAuth credentials
Custom Headers - Service-specific headers (User-Agent, API version)
Tool Collection - All endpoints registered for this API
Source: ApiProvider.java:20-79
Provider Tool GitHub API (https://api.github.com) Create Issue (/repos/{owner}/{repo}/issues) Resend (https://api.resend.com) Send Email (/emails) Tavily (https://api.tavily.com) AI Search (/search)
One provider can have many tools, but each tool belongs to exactly one provider.
Provider Fields
Core Configuration
Field Type Description Example nameString Human-readable name ”GitHub REST API” codeString Unique identifier (auto-generated UUID if blank) “github” baseUrlString API root endpoint (required) “https://api.github.com ” isExportableBoolean Include in exports true
Source: ApiProvider.java:22-26 and CreateApiProviderRequest.java:10-18
Authentication Fields
Field Type Values Description authenticationTypeEnum API_KEY, BEARER_TOKEN, BASIC_AUTHHow to authenticate apiKeyLocationEnum HEADER, QUERY_PARAMETER, IN_BODYWhere to send the key apiKeyNameString e.g., Authorization, x-api-key Header/parameter name apiKeyValueString Encrypted credential Actual API key (encrypted at rest)
Source: ApiProvider.java:27-45
Stored as JSON string, decrypted at runtime:
{
"customHeaders" : {
"User-Agent" : "HandsAI/1.0" ,
"Accept" : "application/vnd.github+json" ,
"X-GitHub-Api-Version" : "2022-11-28"
}
}
Source: ApiProvider.java:36-39 and ToolExecutionService.java:171-185
CRUD Operations
Create Provider
Read Providers
Update Provider
Delete Provider
POST /admin/providers curl -X POST http://localhost:8080/admin/providers \
-H "Content-Type: application/json" \
-d '{
"name": "Resend API",
"code": "resend",
"baseUrl": "https://api.resend.com",
"authenticationType": "BEARER_TOKEN",
"apiKeyLocation": "HEADER",
"apiKeyName": "Authorization",
"apiKeyValue": "re_abc123_yourkey",
"isExportable": true,
"customHeaders": {
"Content-Type": "application/json",
"User-Agent": "HandsAI/1.0"
}
}'
Response: {
"id" : 1 ,
"name" : "Resend API" ,
"code" : "resend" ,
"baseUrl" : "https://api.resend.com" ,
"authenticationType" : "BEARER_TOKEN" ,
"apiKeyLocation" : "HEADER" ,
"apiKeyName" : "Authorization" ,
"isExportable" : true ,
"createdAt" : "2026-03-03T10:00:00Z" ,
"updatedAt" : "2026-03-03T10:00:00Z"
}
Note: apiKeyValue is encrypted before storage and never returned in responses.Source: ProviderController.java:30-34 and ApiProviderService.java:44-81GET /admin/providers List all providers: curl http://localhost:8080/admin/providers
Response: [
{
"id" : 1 ,
"name" : "Resend API" ,
"code" : "resend" ,
"baseUrl" : "https://api.resend.com" ,
"authenticationType" : "BEARER_TOKEN" ,
"isExportable" : true
},
{
"id" : 2 ,
"name" : "GitHub REST API" ,
"code" : "github" ,
"baseUrl" : "https://api.github.com" ,
"authenticationType" : "BEARER_TOKEN" ,
"isExportable" : true
}
]
Source: ProviderController.java:20-23 and ApiProviderService.java:29-34GET /admin/providers/ Get specific provider: curl http://localhost:8080/admin/providers/1
Source: ProviderController.java:25-28PUT /admin/providers/ Update existing provider (all fields optional): curl -X PUT http://localhost:8080/admin/providers/1 \
-H "Content-Type: application/json" \
-d '{
"name": "Resend Email Service",
"apiKeyValue": "re_new_key_xyz789"
}'
Behavior:
Only provided fields are updated
apiKeyValue is re-encrypted if changed
updatedAt timestamp is refreshed
Tool cache automatically refreshes
Source: ProviderController.java:36-41 and ApiProviderService.java:84-143DELETE /admin/providers/ Cascade Deletion Deleting a provider removes ALL associated tools and their parameters due to CascadeType.ALL. Source: ApiProvider.java:72-74
curl -X DELETE http://localhost:8080/admin/providers/1
Response: 204 No ContentSource: ProviderController.java:43-47 and ApiProviderService.java:145-152
Authentication Types
BEARER_TOKEN
API_KEY (Query)
API_KEY (Body)
BASIC_AUTH
Bearer Token Authentication Common for: Modern APIs (GitHub, OpenAI, Resend){
"authenticationType" : "BEARER_TOKEN" ,
"apiKeyLocation" : "HEADER" ,
"apiKeyName" : "Authorization" ,
"apiKeyValue" : "ghp_yourGitHubToken"
}
At runtime, sends: Authorization: Bearer ghp_yourGitHubToken
Source: ToolExecutionService.java:323-325API Key in Query Parameter Common for: Google Maps, Weather APIs{
"authenticationType" : "API_KEY" ,
"apiKeyLocation" : "QUERY_PARAMETER" ,
"apiKeyName" : "key" ,
"apiKeyValue" : "AIza_yourGoogleKey"
}
At runtime, builds URL: https://api.service.com/endpoint?key=AIza_yourGoogleKey
Source: ToolExecutionService.java:224-232 and ToolExecutionService.java:237-288API Key in Request Body Common for: Tavily, some search APIs{
"authenticationType" : "API_KEY" ,
"apiKeyLocation" : "IN_BODY" ,
"apiKeyName" : "api_key" ,
"apiKeyValue" : "tvly-abc123"
}
At runtime, sends JSON: {
"api_key" : "tvly-abc123" ,
"query" : "search term"
}
Source: ToolExecutionService.java:361-370 and NUEVOS_HITOS.json:62-63Basic Authentication Common for: Legacy APIs, webhooks{
"authenticationType" : "BASIC_AUTH" ,
"apiKeyLocation" : "HEADER" ,
"apiKeyName" : "Authorization" ,
"apiKeyValue" : "dXNlcm5hbWU6cGFzc3dvcmQ="
}
Note: apiKeyValue should be base64-encoded username:passwordAt runtime, sends: Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
Source: ToolExecutionService.java:327-329
Dynamic Authentication (OAuth/Sessions)
For APIs requiring token refresh (OAuth, session-based auth):
{
"name" : "Bluesky API" ,
"code" : "bluesky" ,
"baseUrl" : "https://bsky.social/xrpc" ,
"authenticationType" : "BEARER_TOKEN" ,
"apiKeyLocation" : "HEADER" ,
"apiKeyName" : "Authorization" ,
"apiKeyValue" : "" ,
"isDynamicAuth" : true ,
"dynamicAuthUrl" : "https://bsky.social/xrpc/com.atproto.server.createSession" ,
"dynamicAuthMethod" : "POST" ,
"dynamicAuthPayloadType" : "JSON" ,
"dynamicAuthPayloadLocation" : "BODY" ,
"dynamicAuthPayload" : "{ \" identifier \" : \" [email protected] \" , \" password \" : \" app_password \" }" ,
"dynamicAuthTokenExtractionPath" : "accessJwt" ,
"dynamicAuthInvalidationKeywords" : "ExpiredToken,AuthenticationRequired"
}
How It Works
Initial Request: HandsAI POSTs credentials to dynamicAuthUrl
Token Extraction: Uses JSONPath to extract token from response
Token Usage: Injects token into subsequent API calls
Auto-Refresh: Detects invalidation keywords and re-authenticates
Source: NUEVOS_HITOS.json:204-211 and ToolExecutionService.java:75-106
Token Caching Dynamic tokens are cached in-memory by provider ID. They’re automatically invalidated on 401 responses or when response contains invalidation keywords. Source: DynamicTokenManager.java
Real-World Examples
Example 1: GitHub API
curl -X POST http://localhost:8080/admin/providers \
-H "Content-Type: application/json" \
-d '{
"name": "GitHub REST API",
"code": "github",
"baseUrl": "https://api.github.com",
"authenticationType": "BEARER_TOKEN",
"apiKeyLocation": "HEADER",
"apiKeyName": "Authorization",
"apiKeyValue": "ghp_yourPersonalAccessToken",
"isExportable": true,
"customHeaders": {
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
"User-Agent": "HandsAI/1.0"
}
}'
Source: NUEVOS_HITOS.json:110-121
Example 2: Tavily AI Search
curl -X POST http://localhost:8080/admin/providers \
-H "Content-Type: application/json" \
-d '{
"name": "Tavily AI Search",
"code": "tavily",
"baseUrl": "https://api.tavily.com",
"authenticationType": "API_KEY",
"apiKeyLocation": "IN_BODY",
"apiKeyName": "api_key",
"apiKeyValue": "tvly-YourTavilyKey",
"isExportable": true,
"customHeaders": {
"Content-Type": "application/json"
}
}'
Source: NUEVOS_HITOS.json:57-66
Example 3: Google Jules Agent API
curl -X POST http://localhost:8080/admin/providers \
-H "Content-Type: application/json" \
-d '{
"name": "Jules Agent API",
"code": "google-jules-api",
"baseUrl": "https://jules.googleapis.com",
"authenticationType": "API_KEY",
"apiKeyLocation": "HEADER",
"apiKeyName": "x-goog-api-key",
"apiKeyValue": "AIza_YourJulesKey",
"isExportable": true,
"isDynamicAuth": false,
"customHeaders": {
"Content-Type": "application/json"
}
}'
Source: NUEVOS_HITOS.json:235-245
Security Best Practices
API Keys are Encrypted at Rest
HandsAI uses AES encryption for all apiKeyValue fields before database storage. Source: ApiProviderService.java:63 and EncryptionService.java
Keys Never Appear in Exports
Export operations replace real keys with <YOUR_API_KEY> placeholder. Source: ExportService.java:69
Custom Headers are Encrypted Too
Use Environment Variables
For production deployments, inject secrets via environment: export GITHUB_TOKEN = "ghp_xyz"
curl -X POST http://localhost:8080/admin/providers \
-d "{ \" apiKeyValue \" : \" $GITHUB_TOKEN \" , ...}"
Common Issues
Provider not found errors? Verify the provider code matches exactly. Codes are case-sensitive. curl http://localhost:8080/admin/providers | jq '.[].code'
Authentication failing? Check:
authenticationType matches API requirements
apiKeyLocation is correct (header vs query vs body)
apiKeyName matches API documentation (e.g., Authorization vs x-api-key)
API key has required permissions
Debug with: curl http://localhost:8080/admin/tools/api/{tool_id}/validate
Next Steps
Register Tools Add API endpoints to your provider
Import/Export Backup and migrate provider configs
Parameter Types Configure tool input parameters
API Reference Complete endpoint documentation