API Authentication
All Public API requests require authentication via API key. This page covers API key types, authentication methods, and security best practices.
API Key Types
Foir supports three types of API keys, each designed for different use cases:
Production Keys (pk_live_)
- Returns published content only
- Used in production frontend applications
- Ignores
previewparameter - Best for live websites and apps
Preview Keys (pk_preview_)
- Returns latest draft content
- Used for content preview and staging
- Always returns latest version regardless of publish state
- Best for preview deployments and editorial review
Development Keys (pk_dev_)
- Returns any version based on parameters
- Full control over version selection
- Supports
versionId,versionNumber, andversionStateparameters - Best for development and testing
Authentication Methods
Header Authentication (Recommended)
Include the API key in the x-api-key header:
curl -X POST https://api.foir.io/graphql/public \
-H "Content-Type: application/json" \
-H "x-api-key: pk_live_abc123..." \
-d '{"query": "{ resolveRoute(path: \"/\") { record { id } } }"}'Query Parameter Authentication
For webhooks or scenarios where headers aren’t available:
curl -X POST "https://api.foir.io/graphql/public?apiKey=pk_live_abc123..." \
-H "Content-Type: application/json" \
-d '{"query": "{ resolveRoute(path: \"/\") { record { id } } }"}'API Key Scopes
API keys can be configured with specific scopes to limit access:
| Scope | Description |
|---|---|
entities:read | Read entity records |
entities:write | Create and update entities |
entities:delete | Delete entities |
entities:publish | Publish and unpublish entities |
schemas:read | Read project schemas |
workflows:execute | Execute workflows |
Example: A read-only frontend key might have only entities:read scope.
Version Selection (Development Keys Only)
Development keys (pk_dev_) support version selection parameters:
versionState
query GetDraft {
entity(modelKey: "page", id: "123", versionState: LATEST) {
id
data
versionNumber
}
}| Value | Description |
|---|---|
ACTIVE | Published version (default) |
LATEST | Latest draft version |
SPECIFIC | Use with versionId or versionNumber |
Specific Version
# By version ID
query GetSpecificVersion {
entity(modelKey: "page", id: "123", versionId: "ver_456") {
id
data
versionNumber
}
}
# By version number
query GetVersionByNumber {
entity(modelKey: "page", id: "123", versionNumber: 5) {
id
data
versionNumber
}
}List Available Versions
query ListVersions {
listEntityVersions(entityId: "123", limit: 10) {
versions {
id
versionNumber
state
createdAt
isActive
isCurrentDraft
}
total
}
}Security Best Practices
Environment Variables
Never hardcode API keys. Use environment variables:
// Next.js / Node.js
const apiKey = process.env.FOIR_API_KEY;
// Vite
const apiKey = import.meta.env.VITE_FOIR_API_KEY;Server-Side Only
Keep API keys on the server side when possible:
// Next.js App Router - Server Component
async function getPageData(path: string) {
const response = await fetch('https://api.foir.io/graphql/public', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': process.env.FOIR_API_KEY!, // Server-side only
},
body: JSON.stringify({
query: RESOLVE_ROUTE_QUERY,
variables: { path },
}),
});
return response.json();
}Client-Side Keys
If you must use API keys client-side:
- Use production keys only (read-only)
- Enable domain restrictions in API key settings
- Configure rate limiting per key
- Monitor usage in the admin dashboard
Key Rotation
Rotate API keys periodically:
- Create a new key with the same scopes
- Update your application to use the new key
- Verify the new key works
- Disable the old key
- Delete the old key after a grace period
Troubleshooting
”UNAUTHENTICATED” Error
{
"errors": [{
"message": "Authentication required",
"extensions": { "code": "UNAUTHENTICATED" }
}]
}Causes:
- Missing
x-api-keyheader - Invalid or expired API key
- Key disabled in admin
”FORBIDDEN” Error
{
"errors": [{
"message": "Insufficient permissions",
"extensions": { "code": "FORBIDDEN" }
}]
}Causes:
- API key lacks required scope
- Domain restriction mismatch
- Attempting to access other project’s data
Last updated on