Learn how to authenticate with OAuth 2.0 access tokens
| API Key | Label | Last Used | |
|---|---|---|---|
Authentication Guide
Simple,secure B2B authentication for the Speculo API.
Overview
The Speculo API uses long-lived OAuth 2.0 access tokens for server-to-server integrations. No complex refresh logic, no expiry tracking—just get a token once and use it.
Need to understand permissions? Check out OAuth Scopes.
Who does what?
- Speculo support provisions your OAuth credentials (clientId, refreshToken, orgId)
- You exchange the refresh token for an access token and use it on all API requests
Step 1: Collect your credentials
You'll receive these from the Speculo team (or provision them via the internal admin API):
| Name | Description | Example |
|---|---|---|
clientId | Identifies your OAuth client | client_abc123 |
refreshToken | Long-lived secret for minting access tokens | rt_live_xxxxxxxxx |
orgId | Your organization identifier | org_abc123 |
scopes | Permissions granted to the client | ["contacts.read","contacts.write"] |
Security: Treat the refresh token like a password. Store it securely (environment variables, secret manager, etc.).
Step 2: Exchange refresh token for an access token
Call this once when you first set up your integration:
curl -X POST https://speculo-api-oauth-54875993561.us-central1.run.app/token \
-H "Content-Type: application/json" \
-d '{
"clientId": "client_abc123",
"refreshToken": "rt_live_xxxxxxxxx"
}'
Example response:
{
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"tokenType": "Bearer",
"scope": "contacts.read contacts.write",
"scopes": ["contacts.read", "contacts.write"]
}
Important: This access token does not expire. Save it and reuse it for all API requests.
Step 3: Call the APIs
Use the access token on every request. Services can optionally enforce scopes by decoding the JWT or calling /introspect.
curl -X GET "https://api.speculo.ai/v1/contacts?pageSize=50" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
Example with Python:
import requests
ACCESS_TOKEN = "eyJhbGciOiJIUzI1NiIs..."
headers = {
"Authorization": f"Bearer {ACCESS_TOKEN}"
}
response = requests.get(
"https://speculo-api-contacts-uc.a.run.app/v1/contacts",
headers=headers,
params={"pageSize": 50}
)
contacts = response.json()
Example with Node.js:
const axios = require('axios');
const ACCESS_TOKEN = 'eyJhbGciOiJIUzI1NiIs...';
const response = await axios.get(
'https://api.speculo.ai/v1/contacts',
{
headers: {
'Authorization': `Bearer ${ACCESS_TOKEN}`
},
params: {
pageSize: 50
}
}
);
const contacts = response.data;
Token lifecycle
Long-lived tokens (default)
Access tokens never expire by default. This simplifies B2B integrations:
- ✅ No refresh logic needed
- ✅ No token expiry tracking
- ✅ Reliable integrations that don't break
When to get a new token
Only call /token again when:
- Rotating credentials - Periodic security rotation (recommended every 6-12 months)
- Revoking access - When you need to invalidate the old token
- Team member leaves - When someone with access to credentials leaves
How to rotate credentials
- Call
/tokenwith your existing refresh token to get a new access token - Update your application to use the new access token
- (Optional) Contact Speculo support to revoke the old refresh token
Security best practices
Do:
- ✅ Store refresh tokens in environment variables or secret managers
- ✅ Use HTTPS for all API calls (enforced by our services)
- ✅ Rotate credentials periodically (every 6-12 months)
- ✅ Request only the scopes you need (principle of least privilege)
- ✅ Revoke credentials when team members with access leave
Don't:
- ❌ Hard-code refresh tokens in your source code
- ❌ Commit credentials to version control
- ❌ Share refresh tokens via email or messaging apps
- ❌ Reuse the same credentials across multiple environments
Troubleshooting
401 Unauthorized on /token
Possible causes:
- Incorrect
clientIdorrefreshToken - Whitespace/newlines when copying credentials
- Refresh token has been revoked
Solution: Verify credentials and check for copy/paste errors. Contact Speculo support if the issue persists.
401 Unauthorized on API calls
Possible causes:
- Missing or malformed
Authorizationheader - Access token was not properly obtained from
/token - Refresh token was revoked (and access token is no longer valid)
Solution: Verify the Authorization: Bearer {token} header format and ensure you're using the access token from the /token response.
403 Forbidden
Possible causes:
- Client status is not "active" (may be revoked)
- Organization permissions issue
- Access token missing a required scope
Solution: Contact Speculo support to verify client status/scopes, or request new credentials with the appropriate scopes.
Need help?
Contact [email protected] with:
- Your organization ID
- Your client ID
- Timestamp of the failing request (UTC)
- Request/response details (never include refresh tokens or access tokens in email)
We're happy to help with authentication, troubleshooting, or credential rotation.
