Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ Examples include:
### Password Manager Integrations
Skyvern currently supports the following password manager integrations:
- [x] Bitwarden
- [x] Custom Credential Service (HTTP API)
- [ ] 1Password
- [ ] LastPass

Expand Down
203 changes: 203 additions & 0 deletions fern/credentials/custom-credential-service.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
---
title: Custom Credential Service
subtitle: Integrate your own HTTP API for credential management
slug: credentials/custom-credential-service
---

Skyvern supports integrating with custom HTTP APIs for credential management, allowing you to use your existing credential infrastructure instead of third-party services.

## Overview

The custom credential service feature enables Skyvern to store and retrieve credentials from external HTTP APIs. This is perfect for organizations that:

- Have existing credential management systems
- Need to maintain credentials in their own infrastructure
- Want to integrate with proprietary credential vaults
- Require custom authentication flows

## API Contract

Your custom credential service must implement these HTTP endpoints:

### Create Credential
```http
POST {API_BASE_URL}
Authorization: Bearer {API_TOKEN}
Content-Type: application/json

{
"name": "My Credential",
"type": "password",
"username": "[email protected]",
"password": "secure_password",
"totp": "JBSWY3DPEHPK3PXP",
"totp_type": "authenticator"
}
```

**Response:**
```json
{
"id": "cred_123456"
}
```

### Get Credential
```http
GET {API_BASE_URL}/{credential_id}
Authorization: Bearer {API_TOKEN}
```

**Response:**
```json
{
"type": "password",
"username": "[email protected]",
"password": "secure_password",
"totp": "JBSWY3DPEHPK3PXP",
"totp_type": "authenticator"
}
```

### Delete Credential
```http
DELETE {API_BASE_URL}/{credential_id}
Authorization: Bearer {API_TOKEN}
```

**Response:** HTTP 200 (empty body acceptable)

## Configuration

### Environment Variables (Self-hosted)

Set these environment variables in your `.env` file:

```bash
CREDENTIAL_VAULT_TYPE=custom
CUSTOM_CREDENTIAL_API_BASE_URL=https://credentials.company.com/api/v1/credentials
CUSTOM_CREDENTIAL_API_TOKEN=your_api_token_here
```

### Organization Configuration (Cloud)

Use the Skyvern API to configure per-organization:

```http
POST /api/v1/credentials/custom_credential/create
Authorization: Bearer {SKYVERN_API_KEY}
Content-Type: application/json

{
"config": {
"api_base_url": "https://credentials.company.com/api/v1/credentials",
"api_token": "your_api_token_here"
}
}
```

### UI Configuration

1. Navigate to **Settings** → **Custom Credential Service**
2. Enter your API Base URL and API Token
3. Click **Test Connection** to verify connectivity
4. Click **Update Configuration** to save

## Example Implementation

Here's a minimal example using FastAPI:

```python
from fastapi import FastAPI, HTTPException, Depends, Header
from pydantic import BaseModel
from typing import Optional
import uuid

app = FastAPI()

# In-memory storage (use a real database in production)
credentials_store = {}

class CreateCredentialRequest(BaseModel):
name: str
type: str # "password" or "credit_card"
username: Optional[str] = None
password: Optional[str] = None
totp: Optional[str] = None
totp_type: Optional[str] = None

class CredentialResponse(BaseModel):
id: str

def verify_token(authorization: str = Header(...)):
if not authorization.startswith("Bearer "):
raise HTTPException(401, "Invalid authorization header")

token = authorization.split("Bearer ")[1]
if token != "your_expected_api_token":
raise HTTPException(401, "Invalid API token")

@app.post("/api/v1/credentials", response_model=CredentialResponse)
async def create_credential(
request: CreateCredentialRequest,
_: None = Depends(verify_token)
):
credential_id = f"cred_{uuid.uuid4().hex[:12]}"
credentials_store[credential_id] = request.model_dump()
return CredentialResponse(id=credential_id)

@app.get("/api/v1/credentials/{credential_id}")
async def get_credential(
credential_id: str,
_: None = Depends(verify_token)
):
if credential_id not in credentials_store:
raise HTTPException(404, "Credential not found")
return credentials_store[credential_id]

@app.delete("/api/v1/credentials/{credential_id}")
async def delete_credential(
credential_id: str,
_: None = Depends(verify_token)
):
if credential_id not in credentials_store:
raise HTTPException(404, "Credential not found")
del credentials_store[credential_id]
return {"status": "deleted"}
```

## Security Considerations

- API tokens are stored encrypted in the database
- Bearer tokens are transmitted over HTTPS only
- Frontend masks sensitive tokens in the UI
- API credentials are never logged in plaintext
- Implement proper rate limiting and authentication in your API

## Troubleshooting

### Connection Test Fails

1. Verify API base URL is correct and accessible
2. Check that API token is valid
3. Check firewall and network connectivity
4. Note: Connection test only verifies basic connectivity - 404/405 responses are considered successful if the server is reachable

### Credentials Not Created

1. Review API logs for authentication errors
2. Verify request format matches expected schema
3. Ensure API returns `id` in response

### Environment Configuration Not Working

1. Restart Skyvern after setting environment variables
2. Verify `CREDENTIAL_VAULT_TYPE=custom` is set
3. Check both URL and token are provided

## Limitations

- Connection testing verifies network connectivity and basic API reachability but not full endpoint implementation
- API must support all required endpoints (no partial implementation)
- Token rotation requires manual reconfiguration
- No built-in credential synchronization between vaults
8 changes: 8 additions & 0 deletions fern/credentials/introduction.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ If you have your own password manager, Skyvern can integrate with it. Skyvern ca

**Supported password manager types**:
- Bitwarden
- Custom Credential Service (HTTP API)
- 1Password Integration (Private beta)

**Coming Soon**:
Expand Down Expand Up @@ -88,6 +89,13 @@ Contact [Skyvern Support](mailto:[email protected]) if you want access to the
>
(coming soon) Securely manage your passwords with LastPass
</Card>
<Card
title="Custom Credential Service"
icon="api"
href="/credentials/custom-credential-service"
>
Integrate your own HTTP API for credential management
</Card>
<Card
title="Keeper Integration"
icon="lock-keyhole"
Expand Down
2 changes: 2 additions & 0 deletions fern/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ navigation:
path: credentials/totp.mdx
- page: Bitwarden
path: credentials/bitwarden.mdx
- page: Custom Credential Service
path: credentials/custom-credential-service.mdx
- section: Browser Sessions (Beta)
contents:
- page: Introduction
Expand Down
23 changes: 23 additions & 0 deletions skyvern-frontend/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,29 @@ export interface AzureClientSecretCredentialResponse {
token: AzureOrganizationAuthToken;
}

export interface CustomCredentialServiceConfig {
api_base_url: string;
api_token: string;
}

export interface CustomCredentialServiceOrganizationAuthToken {
id: string;
organization_id: string;
token: string; // JSON string containing CustomCredentialServiceConfig
created_at: string;
modified_at: string;
token_type: string;
valid: boolean;
}

export interface CreateCustomCredentialServiceConfigRequest {
config: CustomCredentialServiceConfig;
}

export interface CustomCredentialServiceConfigResponse {
token: CustomCredentialServiceOrganizationAuthToken;
}

// TODO complete this
export const ActionTypes = {
InputText: "input_text",
Expand Down
Loading