📖 Developer Documentation
Complete guide to integrating with skunk.to
📑 Table of Contents
🚀 Getting Started
skunk.to provides two ways to integrate with our platform:
- OAuth 2.0 - Enable "Login with skunk.to" on your website, allowing users to authenticate using their skunk.to account.
- API Keys - Access the API directly to upload files and create pastes programmatically.
To get started, visit the Developer Dashboard to create your OAuth application or API keys.
🔐 OAuth 2.0 / "Login with skunk.to"
Overview
skunk.to implements the OAuth 2.0 Authorization Code flow with support for PKCE (Proof Key for Code Exchange). This allows users to securely authorize your application to access their skunk.to account without sharing their password.
Our OAuth implementation follows RFC 6749 (OAuth 2.0) and RFC 7636 (PKCE), making it compatible with any OAuth 2.0 client library.
Setting Up Your Application
- Go to the Developer Dashboard
- Click "Create Application"
- Fill in your application details:
- Name - The name users will see when authorizing
- Redirect URIs - URLs where users will be redirected after authorization
- Application Type:
- Confidential - For server-side apps that can securely store a client secret
- Public - For SPAs and mobile apps (uses PKCE instead of client secret)
- Save your Client ID and Client Secret (for confidential apps)
Your Client Secret is only shown once. Store it securely! If you lose it, you'll need to regenerate it.
Authorization Flow
Step 1: Redirect to Authorization
Direct users to the authorization endpoint:
GET https://skunk.to/oauth/authorize?
response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=https://yourapp.com/callback
&scope=openid profile:read
&state=RANDOM_STATE_STRING
| Parameter | Description |
|---|---|
| response_typerequired | Must be code |
| client_idrequired | Your application's Client ID |
| redirect_urirequired | Must match one of your registered redirect URIs |
| scopeoptional | Space-separated list of scopes. Defaults to openid |
| staterecommended | Random string to prevent CSRF attacks. Will be returned unchanged. |
| code_challengePKCE | SHA-256 hash of code_verifier (for public clients) |
| code_challenge_methodPKCE | Must be S256 |
Step 2: User Authorizes
The user will see a consent screen showing your app name and requested permissions. After approval, they're redirected to your redirect_uri with an authorization code:
https://yourapp.com/callback?code=AUTH_CODE&state=RANDOM_STATE_STRING
Step 3: Exchange Code for Tokens
Exchange the authorization code for access and refresh tokens:
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=AUTH_CODE
&redirect_uri=https://yourapp.com/callback
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "dGhpcyBpcyBhIHJlZnJl...",
"scope": "openid profile:read"
}
Step 4: Use the Access Token
Include the access token in API requests:
GET https://skunk.to/oauth/userinfo
Authorization: Bearer ACCESS_TOKEN
Step 5: Refresh Tokens
When the access token expires, use the refresh token to get a new one:
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token=YOUR_REFRESH_TOKEN
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
PKCE for Public Clients
For SPAs, mobile apps, and other public clients that can't securely store a client secret, use PKCE:
Generate Code Verifier and Challenge
// JavaScript example
function generateCodeVerifier() {
const array = new Uint8Array(32);
crypto.getRandomValues(array);
return btoa(String.fromCharCode(...array))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}
async function generateCodeChallenge(verifier) {
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
const digest = await crypto.subtle.digest('SHA-256', data);
return btoa(String.fromCharCode(...new Uint8Array(digest)))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
}
const codeVerifier = generateCodeVerifier();
const codeChallenge = await generateCodeChallenge(codeVerifier);
// Store codeVerifier securely (e.g., sessionStorage)
// Include codeChallenge in authorization request
Include in authorization request:
&code_challenge=CODE_CHALLENGE
&code_challenge_method=S256
Include in token exchange:
&code_verifier=CODE_VERIFIER
Available Scopes
| Scope | Description |
|---|---|
| openid | Verify user identity (required for login) |
| profile:read | Read basic profile information (username, ID) |
| profile:write | Update profile information |
| uploads:read | View user's uploaded files |
| uploads:write | Upload files on behalf of user |
| pastes:read | View user's pastes |
| pastes:write | Create pastes on behalf of user |
OAuth Endpoints
| Endpoint | URL |
|---|---|
| Authorization | https://skunk.to/oauth/authorize |
| Token | https://skunk.to/oauth/token |
| User Info | https://skunk.to/oauth/userinfo |
| Token Revocation | https://skunk.to/oauth/revoke |
Login Button Integration
Add a "Login with skunk.to" button to your site:
🦨 Login with skunk.to<!-- HTML -->
<a href="https://skunk.to/oauth/authorize?
response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=https://yourapp.com/callback
&scope=openid profile:read
&state=RANDOM_STATE"
class="skunk-login-btn">
🦨 Login with skunk.to
</a>
<!-- CSS -->
<style>
.skunk-login-btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
background: linear-gradient(135deg, #7c3aed, #a855f7);
color: white;
padding: 0.75rem 1.5rem;
border-radius: 8px;
font-weight: 600;
text-decoration: none;
font-family: system-ui, sans-serif;
}
.skunk-login-btn:hover { opacity: 0.9; }
</style>
🔑 API Keys
API keys provide direct access to the skunk.to API without requiring user authorization. They're ideal for:
- Automated scripts and tools
- Server-side integrations
- ShareX configuration
- CI/CD pipelines
Using API Keys
Include your API key in the Authorization header:
Authorization: Bearer sk_live_xxxx_xxxxxxxxxxxxxxxx
Or as a query parameter (not recommended for security):
GET https://skunk.to/api/upload?api_key=sk_live_xxxx_xxxxxxxxxxxxxxxx
API Key Scopes
When creating an API key, you can limit its permissions to specific scopes. This follows the principle of least privilege.
| Scope | Allows |
|---|---|
| uploads:read | List and view your uploads |
| uploads:write | Upload new files, delete uploads |
| pastes:read | List and view your pastes |
| pastes:write | Create new pastes, delete pastes |
| profile:read | View account information |
📡 API Reference
User Info
Returns information about the authenticated user.
Required Scope: openid or profile:read
{
"sub": "user_abc123",
"name": "johndoe",
"preferred_username": "johndoe",
"picture": null,
"updated_at": 1704067200
}
File Uploads
Upload a file.
Required Scope: uploads:write
curl -X POST "https://skunk.to/api/upload" \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "file=@/path/to/file.png"
Response:
{
"success": true,
"id": "abc123",
"url": "https://skunk.to/abc123",
"raw_url": "https://skunk.to/abc123/raw",
"deletion_key": "del_xyz789",
"deletion_url": "https://skunk.to/abc123?delete=del_xyz789"
}
Pastes
Create a new paste.
Required Scope: pastes:write
curl -X POST "https://skunk.to/api/paste" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "console.log(\"Hello World\");",
"syntax": "javascript",
"title": "My Code Snippet"
}'
Response:
{
"success": true,
"id": "paste123",
"url": "https://skunk.to/p/paste123",
"raw_url": "https://skunk.to/p/paste123/raw",
"deletion_key": "del_abc456",
"deletion_url": "https://skunk.to/p/paste123?delete=del_abc456"
}
💻 Code Examples
Python - OAuth Client
import requests
from urllib.parse import urlencode
CLIENT_ID = "sk_your_client_id"
CLIENT_SECRET = "sks_your_client_secret"
REDIRECT_URI = "https://yourapp.com/callback"
# Step 1: Generate authorization URL
auth_params = {
"response_type": "code",
"client_id": CLIENT_ID,
"redirect_uri": REDIRECT_URI,
"scope": "openid profile:read",
"state": "random_state_string"
}
auth_url = f"https://skunk.to/oauth/authorize?{urlencode(auth_params)}"
print(f"Redirect user to: {auth_url}")
# Step 2: Exchange code for tokens (in your callback handler)
def exchange_code(code):
response = requests.post("https://skunk.to/oauth/token", data={
"grant_type": "authorization_code",
"code": code,
"redirect_uri": REDIRECT_URI,
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET
})
return response.json()
# Step 3: Get user info
def get_user_info(access_token):
response = requests.get("https://skunk.to/oauth/userinfo", headers={
"Authorization": f"Bearer {access_token}"
})
return response.json()
JavaScript - File Upload with API Key
const API_KEY = 'sk_live_xxxx_xxxxxxxxxxxxxxxx';
async function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
const response = await fetch('https://skunk.to/api/upload', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`
},
body: formData
});
return response.json();
}
// Usage
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', async (e) => {
const result = await uploadFile(e.target.files[0]);
console.log('Uploaded:', result.url);
});
cURL - Create Paste
curl -X POST "https://skunk.to/api/paste" \
-H "Authorization: Bearer sk_live_xxxx_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"content": "Hello, World!",
"syntax": "plaintext",
"title": "My First Paste"
}'
⚠️ Error Handling
OAuth Errors
OAuth errors follow RFC 6749 format:
{
"error": "invalid_grant",
"error_description": "Authorization code has expired"
}
| Error Code | Description |
|---|---|
| invalid_request | Missing or invalid parameters |
| invalid_client | Unknown client or invalid credentials |
| invalid_grant | Invalid, expired, or revoked authorization code |
| unauthorized_client | Client not authorized for this grant type |
| unsupported_grant_type | Grant type not supported |
| invalid_scope | Requested scope is invalid or unknown |
| access_denied | User denied authorization |
API Errors
{
"success": false,
"error": "Invalid API key"
}
HTTP Status Codes
| Status | Meaning |
|---|---|
| 200 | Success |
| 400 | Bad request - check parameters |
| 401 | Unauthorized - invalid or expired token |
| 403 | Forbidden - insufficient permissions |
| 404 | Resource not found |
| 429 | Rate limited - slow down |
| 500 | Server error |
If you have questions or run into issues, check our Developer Dashboard or contact support.