Authentication
SingleFile uses OAuth 2.0 Client Credentials Flow. You'll exchange a
client_idandclient_secretfor a bearer token, then include that token in every API request.
How It Works
Your App SingleFile OAuth
| |
|--- POST /o/token/ -------------->|
| (client_id + client_secret) |
| |
|<--- access_token (1 hour) -------|
| |
|--- GET /external-api/v1/... ---->| SingleFile API
| (Authorization: Bearer token) |
| |
|<--- JSON response ---------------|
Step-by-Step
| Step | Action | Details |
|---|---|---|
| 1 | Get credentials | SingleFile provides your client_id and client_secret |
| 2 | Request token | POST to the OAuth endpoint with your credentials |
| 3 | Make API calls | Include the token in the Authorization header |
| 4 | Refresh before expiry | Tokens expire after 3600 seconds (1 hour) |
Get Your Bearer Token
curl -X POST https://api.demo.singlefile.io/o/token/ \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET"import requests
response = requests.post(
"https://api.demo.singlefile.io/o/token/",
headers={"Content-Type": "application/x-www-form-urlencoded"},
data={
"grant_type": "client_credentials",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
}
)
access_token = response.json()["access_token"]const formData = new URLSearchParams({
grant_type: "client_credentials",
client_id: "YOUR_CLIENT_ID",
client_secret: "YOUR_CLIENT_SECRET"
});
const response = await fetch("https://api.demo.singlefile.io/o/token/", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: formData
});
const { access_token } = await response.json();package main
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
)
func main() {
data := url.Values{
"grant_type": {"client_credentials"},
"client_id": {"YOUR_CLIENT_ID"},
"client_secret": {"YOUR_CLIENT_SECRET"},
}
resp, err := http.Post(
"https://api.demo.singlefile.io/o/token/",
"application/x-www-form-urlencoded",
strings.NewReader(data.Encode()),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result struct {
AccessToken string `json:"access_token"`
}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Println("Access token:", result.AccessToken)
}using System.Net.Http;
using System.Text.Json;
var client = new HttpClient();
var form = new FormUrlEncodedContent(new Dictionary<string, string> {
["grant_type"] = "client_credentials",
["client_id"] = "YOUR_CLIENT_ID",
["client_secret"] = "YOUR_CLIENT_SECRET"
});
var response = await client.PostAsync(
"https://api.demo.singlefile.io/o/token/", form);
var json = await response.Content.ReadAsStringAsync();
var accessToken = JsonDocument.Parse(json).RootElement
.GetProperty("access_token").GetString()!;Response:
{
"access_token": "your_bearer_token_here",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "read write"
}Token Expiration & Refresh
Tokens expire after 1 hour (3600 seconds). Your app should refresh proactively before expiry to avoid failed requests.
#!/bin/bash
# Auto-refreshing token helper for shell scripts
CLIENT_ID="YOUR_CLIENT_ID"
CLIENT_SECRET="YOUR_CLIENT_SECRET"
TOKEN_FILE="/tmp/singlefile_token.json"
get_token() {
local now=$(date +%s)
# Re-use cached token if still valid (with 60s buffer)
if [ -f "$TOKEN_FILE" ]; then
local expires_at=$(jq -r '.expires_at' "$TOKEN_FILE" 2>/dev/null)
local token=$(jq -r '.access_token' "$TOKEN_FILE" 2>/dev/null)
if [ -n "$expires_at" ] && [ "$now" -lt "$((expires_at - 60))" ]; then
echo "$token"
return
fi
fi
# Fetch a new token
local response=$(curl -s -X POST https://api.demo.singlefile.io/o/token/ \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=${CLIENT_ID}" \
-d "client_secret=${CLIENT_SECRET}")
local token=$(echo "$response" | jq -r '.access_token')
local expires_in=$(echo "$response" | jq -r '.expires_in')
local expires_at=$((now + expires_in))
echo "$response" | jq --argjson ea "$expires_at" '. + {expires_at: $ea}' > "$TOKEN_FILE"
echo "$token"
}
# Usage — always returns a valid token
curl -X GET "https://api.demo.singlefile.io/external-api/v1/entities" \
-H "Authorization: Bearer $(get_token)" \
-H "Accept: application/json"import time
import requests
class SingleFileAuth:
def __init__(self, client_id, client_secret):
self.client_id = client_id
self.client_secret = client_secret
self.token = None
self.expires_at = 0
def get_token(self):
if self.token and time.time() < self.expires_at - 60:
return self.token
response = requests.post(
"https://api.demo.singlefile.io/o/token/",
data={
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret
}
)
data = response.json()
self.token = data["access_token"]
self.expires_at = time.time() + data["expires_in"]
return self.token
auth = SingleFileAuth("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET")
# Always returns a valid token, refreshing automatically
headers = {"Authorization": f"Bearer {auth.get_token()}"}class SingleFileAuth {
constructor(clientId, clientSecret) {
this.clientId = clientId;
this.clientSecret = clientSecret;
this.token = null;
this.expiresAt = 0;
}
async getToken() {
if (this.token && Date.now() < this.expiresAt - 60_000) {
return this.token;
}
const response = await fetch("https://api.demo.singlefile.io/o/token/", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "client_credentials",
client_id: this.clientId,
client_secret: this.clientSecret,
}),
});
const data = await response.json();
this.token = data.access_token;
this.expiresAt = Date.now() + data.expires_in * 1000;
return this.token;
}
}
const auth = new SingleFileAuth("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET");
// Always returns a valid token, refreshing automatically
const headers = { Authorization: `Bearer ${await auth.getToken()}` };package main
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"sync"
"time"
)
type SingleFileAuth struct {
ClientID string
ClientSecret string
mu sync.Mutex
token string
expiresAt time.Time
}
func (a *SingleFileAuth) GetToken() (string, error) {
a.mu.Lock()
defer a.mu.Unlock()
if a.token != "" && time.Now().Before(a.expiresAt.Add(-60*time.Second)) {
return a.token, nil
}
resp, err := http.PostForm("https://api.demo.singlefile.io/o/token/", url.Values{
"grant_type": {"client_credentials"},
"client_id": {a.ClientID},
"client_secret": {a.ClientSecret},
})
if err != nil {
return "", err
}
defer resp.Body.Close()
var data struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
}
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
return "", err
}
a.token = data.AccessToken
a.expiresAt = time.Now().Add(time.Duration(data.ExpiresIn) * time.Second)
return a.token, nil
}
func main() {
auth := &SingleFileAuth{
ClientID: "YOUR_CLIENT_ID",
ClientSecret: "YOUR_CLIENT_SECRET",
}
// Always returns a valid token, refreshing automatically
token, _ := auth.GetToken()
fmt.Println("Authorization: Bearer", token)
}using System.Net.Http;
using System.Text.Json;
public class SingleFileAuth
{
private readonly string _clientId;
private readonly string _clientSecret;
private readonly HttpClient _http = new();
private string? _token;
private DateTimeOffset _expiresAt;
public SingleFileAuth(string clientId, string clientSecret)
{
_clientId = clientId;
_clientSecret = clientSecret;
}
public async Task<string> GetTokenAsync()
{
if (_token is not null && DateTimeOffset.UtcNow < _expiresAt.AddSeconds(-60))
return _token;
var form = new FormUrlEncodedContent(new Dictionary<string, string> {
["grant_type"] = "client_credentials",
["client_id"] = _clientId,
["client_secret"] = _clientSecret
});
var response = await _http.PostAsync(
"https://api.demo.singlefile.io/o/token/", form);
var json = await response.Content.ReadAsStringAsync();
var doc = JsonDocument.Parse(json).RootElement;
_token = doc.GetProperty("access_token").GetString()!;
var expiresIn = doc.GetProperty("expires_in").GetInt32();
_expiresAt = DateTimeOffset.UtcNow.AddSeconds(expiresIn);
return _token;
}
}
// Usage
var auth = new SingleFileAuth("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET");
// Always returns a valid token, refreshing automatically
var token = await auth.GetTokenAsync();
var headers = new Dictionary<string, string> {
["Authorization"] = $"Bearer {token}"
};Security Best Practices
Never expose credentials in client-side code, public repos, or logs.
- Store
client_idandclient_secretin environment variables or a secrets manager - Never log or print bearer tokens in production
- Use HTTPS for all API requests
- Rotate credentials periodically
Updated about 1 month ago
