Authentication

SingleFile uses OAuth 2.0 Client Credentials Flow. You'll exchange a client_id and client_secret for 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

StepActionDetails
1Get credentialsSingleFile provides your client_id and client_secret
2Request tokenPOST to the OAuth endpoint with your credentials
3Make API callsInclude the token in the Authorization header
4Refresh before expiryTokens 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_id and client_secret in environment variables or a secrets manager
  • Never log or print bearer tokens in production
  • Use HTTPS for all API requests
  • Rotate credentials periodically