API Key Integration Guide
This guide covers everything you need to integrate with the GYBC platform using API keys, including key management, authentication, scopes, rate limiting, and best practices.
Overview
API keys provide programmatic, backend-to-backend access to the GYBC platform. There are two key types:
| Key Type | Prefix | Use Case |
|---|---|---|
| Secret key | sk_live_* / sk_test_* | Server-side integration from your backend |
| Publishable key | pk_live_* / pk_test_* | Client-side apps (iOS, web) — requires a user JWT alongside it |
Secret keys authenticate your backend directly. Publishable keys are safe to embed in client apps but must always be paired with a user JWT (see Multi-Tenancy for client app setup).
The full key value is only returned once at creation time. Store it securely — it cannot be retrieved again.
Using API Keys in Requests
Pass your API key in the X-API-Key header. All endpoints use POST with a JSON body.
curl -X POST https://api.yocaso.dev/api/v1/llm/gateway/list-threads \
-H "X-API-Key: sk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{}'
User Impersonation
Secret keys with the users:impersonate scope can act on behalf of a specific user by setting the X-On-Behalf-Of header:
curl -X POST https://api.yocaso.dev/api/v1/llm/gateway/send-message \
-H "X-API-Key: sk_live_your_key_here" \
-H "X-On-Behalf-Of: user_123" \
-H "Content-Type: application/json" \
-d '{
"conversation_key": "conv_abc",
"user_message": {"role": "user", "content": "Hello"}
}'
Managing API Keys
API key management endpoints require JWT authentication (not API keys). You cannot use an API key to manage other API keys.
Create a Secret Key
POST /api/v1/api-keys/create
Request:
{
"name": "production-backend",
"description": "Production backend server",
"permissions": ["conversations:read", "conversations:write"],
"rate_limit": {
"requests_per_minute": 1000
},
"expires_at": "2026-12-31T23:59:59Z"
}
Response:
{
"result": {
"key_id": "key_abc123",
"key": "sk_live_full_key_value_here",
"key_prefix": "sk_live_2hfK"
}
}
| Field | Description |
|---|---|
key_id | Unique identifier — use this for all management operations |
key | The full key value — returned only once, store it securely |
key_prefix | First few characters for display and log identification |
Create a Publishable Key
Publishable keys are designed for client-side apps (iOS, web). They must always be paired with a user JWT, and require OIDC configuration so the platform can validate the JWT.
POST /api/v1/api-keys/create
Request:
{
"name": "socayo-ios",
"description": "Socayo iOS app",
"keyType": "KEY_TYPE_PUBLISHABLE",
"permissions": ["conversations:read", "conversations:write"],
"publishableConfig": {
"oidc": {
"issuer": "https://securetoken.google.com/your-firebase-project",
"jwksUrl": "https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com",
"audience": "your-firebase-project"
}
}
}
Response:
{
"result": {
"key_id": "key_xyz789",
"key": "pk_live_full_key_value_here",
"key_prefix": "pk_live_9kLm"
}
}
The OIDC config tells the platform how to validate user JWTs presented alongside this key:
| Field | Description |
|---|---|
issuer | Expected iss claim in the JWT |
jwksUrl | URL to fetch the public keys for JWT signature verification |
audience | Expected aud claim in the JWT |
userIdClaim | (Optional) JWT claim to extract the user ID from. Defaults to sub |
For Firebase Auth, use https://securetoken.google.com/<project-id> as the issuer and <project-id> as the audience. See Multi-Tenancy for the full client app setup.
List Keys
POST /api/v1/api-keys/list
Request:
{
"page": 1,
"per_page": 20
}
Response:
{
"keys": [
{
"key_id": "key_abc123",
"name": "production-backend",
"key_prefix": "sk_live_2hfK",
"description": "Production backend server",
"permissions": ["conversations:read", "conversations:write"],
"rate_limit": {
"requests_per_minute": 1000,
"requests_per_hour": 0,
"burst_size": 0
},
"created_at": "2025-03-01T10:00:00Z",
"expires_at": "2026-12-31T23:59:59Z",
"environment": "live"
}
],
"pagination": {
"total": 1,
"page": 1,
"per_page": 20,
"total_pages": 1
}
}
Get Key Details
POST /api/v1/api-keys/get
Request:
{
"key_id": "key_abc123"
}
Update a Key
Uses a field mask to specify which fields to modify. Only fields listed in update_mask are changed.
POST /api/v1/api-keys/update
Request:
{
"key_id": "key_abc123",
"name": "production-backend-v2",
"permissions": ["conversations:read", "conversations:write", "plans:read"],
"update_mask": "name,permissions"
}
Revoke a Key
Permanently revokes an API key, making it immediately unusable. This action cannot be undone.
POST /api/v1/api-keys/revoke
Request:
{
"key_id": "key_abc123"
}
Get Usage Statistics
Returns verification statistics for a specific API key.
POST /api/v1/api-keys/usage
Request:
{
"key_id": "key_abc123"
}
Response:
{
"result": {
"total_verifications": 15230,
"successful_verifications": 15100,
"failed_verifications": 130
}
}
Scopes and Permissions
Scopes control what operations an API key can perform. Assign scopes when creating or updating a key via the permissions field.
Available Scopes
| Scope | Description |
|---|---|
* | Full access (all scopes) |
conversations:read | Read conversations and threads |
conversations:write | Send messages, create threads |
plans:read | Read coaching plans |
plans:write | Create and modify coaching plans |
users:read | Read user profiles |
users:impersonate | Act on behalf of users via X-On-Behalf-Of |
billing:read | Read billing information |
billing:write | Modify billing settings |
invoices:read | Read invoices |
invoices:write | Modify invoices |
Scope Matching Rules
- Exact match:
conversations:readgrants only read access to conversations - Wildcard:
*grants access to all scopes - Prefix wildcard:
users:*grants all user-related scopes (users:read,users:impersonate, etc.)
Publishable Key Restrictions
Publishable keys (pk_*) automatically have the following scopes blocked for security:
api_keys:*— Cannot manage API keyswebhooks:*— Cannot manage webhookstenants:*— Cannot manage tenantsbilling:*— Cannot access billingsystem:*— Cannot access system operationsusers:impersonate— Cannot impersonate users*— Cannot have wildcard access
Rate Limiting
Rate limits can be configured per API key to control request throughput.
Configuration
Set rate limits when creating or updating a key:
{
"rate_limit": {
"requests_per_minute": 1000,
"requests_per_hour": 50000,
"burst_size": 100
}
}
| Field | Description |
|---|---|
requests_per_minute | Maximum requests per minute (0 = unlimited) |
requests_per_hour | Maximum requests per hour (0 = unlimited) |
burst_size | Maximum burst size for token bucket algorithm (0 = use default) |
Rate Limit Response
When a request is rate-limited, the API returns HTTP 429 (Too Many Requests). The response body includes the current rate limit status:
{
"rate_limit": {
"limit": 1000,
"remaining": 0,
"reset_at": "2025-03-06T12:05:00Z"
}
}
| Field | Description |
|---|---|
limit | Maximum requests in the current window |
remaining | Requests remaining in the current window |
reset_at | When the rate limit window resets (RFC 3339 timestamp) |
Key Rotation
API key rotation is a deliberate three-step process that gives you full control over the transition window. There is no atomic "rotate" endpoint by design — an atomic rotation would invalidate the old key before your consumers can switch, causing downtime.
Rotation Procedure
- Create a new key with the same permissions as the existing one:
curl -X POST https://api.yocaso.dev/api/v1/api-keys/create \
-H "Authorization: Bearer <admin-jwt>" \
-H "Content-Type: application/json" \
-d '{
"name": "production-backend-rotated",
"description": "Rotated key - March 2025",
"permissions": ["conversations:read", "conversations:write"]
}'
-
Update all consumers to use the new key. Both old and new keys work during this transition window.
-
Revoke the old key once all consumers have been updated:
curl -X POST https://api.yocaso.dev/api/v1/api-keys/revoke \
-H "Authorization: Bearer <admin-jwt>" \
-H "Content-Type: application/json" \
-d '{"key_id": "old_key_id"}'
Best Practices
- Rotate regularly — establish a rotation schedule (e.g., every 90 days)
- Use key names with dates — e.g.,
production-backend-2025-03for easy tracking - Monitor usage — check usage stats before revoking to confirm the old key is no longer in use
- Set expiration dates — use
expires_atto enforce automatic expiration as a safety net
Code Examples
cURL
# List threads
curl -X POST https://api.yocaso.dev/api/v1/llm/gateway/list-threads \
-H "X-API-Key: sk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{}'
# Send a message (with user impersonation)
curl -X POST https://api.yocaso.dev/api/v1/llm/gateway/send-message \
-H "X-API-Key: sk_live_your_key_here" \
-H "X-On-Behalf-Of: user_123" \
-H "Content-Type: application/json" \
-d '{
"conversation_key": "conv_abc",
"user_message": {"role": "user", "content": "Hello"}
}'
Python
import requests
BASE_URL = "https://api.yocaso.dev"
API_KEY = "sk_live_your_key_here"
headers = {
"X-API-Key": API_KEY,
"Content-Type": "application/json",
}
# List threads
response = requests.post(
f"{BASE_URL}/api/v1/llm/gateway/list-threads",
headers=headers,
json={},
)
print(response.json())
# Send a message on behalf of a user
response = requests.post(
f"{BASE_URL}/api/v1/llm/gateway/send-message",
headers={**headers, "X-On-Behalf-Of": "user_123"},
json={
"conversation_key": "conv_abc",
"user_message": {"role": "user", "content": "Hello"},
},
)
print(response.json())
Go
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
const (
baseURL = "https://api.yocaso.dev"
apiKey = "sk_live_your_key_here"
)
func main() {
// List threads
body, _ := json.Marshal(map[string]any{})
req, _ := http.NewRequest("POST",
baseURL+"/api/v1/llm/gateway/list-threads",
bytes.NewReader(body))
req.Header.Set("X-API-Key", apiKey)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
fmt.Println("Status:", resp.Status)
fmt.Println("Body:", string(respBody))
}
Node.js
const BASE_URL = "https://api.yocaso.dev";
const API_KEY = "sk_live_your_key_here";
// List threads
const response = await fetch(
`${BASE_URL}/api/v1/llm/gateway/list-threads`,
{
method: "POST",
headers: {
"X-API-Key": API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
}
);
const data = await response.json();
console.log(data);
// Send a message on behalf of a user
const msgResponse = await fetch(
`${BASE_URL}/api/v1/llm/gateway/send-message`,
{
method: "POST",
headers: {
"X-API-Key": API_KEY,
"X-On-Behalf-Of": "user_123",
"Content-Type": "application/json",
},
body: JSON.stringify({
conversation_key: "conv_abc",
user_message: { role: "user", content: "Hello" },
}),
}
);
const msgData = await msgResponse.json();
console.log(msgData);
Error Responses and Troubleshooting
When authentication fails, the API returns an error with a denial_reason field indicating the cause.
Authentication Errors
| Error | HTTP Status | Cause | Fix |
|---|---|---|---|
missing_credentials | 401 | No API key or JWT in request | Add X-API-Key header |
api_key_not_found | 401 | Key doesn't exist | Verify the key value is correct |
api_key_expired | 401 | Key has passed its expiration date | Create a new key |
api_key_disabled | 401 | Key is disabled | Contact your admin to re-enable |
api_key_revoked | 401 | Key has been revoked | Create a new key — revocation is permanent |
api_key_invalid | 401 | Generic invalid key | Verify the key format (sk_live_* or sk_test_*) |
insufficient_scope | 403 | Key lacks the required scope | Update the key's permissions or create a new key with the needed scope |
rate_limited | 429 | Key hit its rate limit | Wait for the reset window or increase the rate limit |
Publishable Key Errors
| Error | HTTP Status | Cause | Fix |
|---|---|---|---|
publishable_key_requires_jwt | 401 | pk_* key used without a user JWT | Add Authorization: Bearer <jwt> header |
jwt_expired | 401 | User JWT has expired | Refresh the JWT token |
jwt_malformed | 401 | JWT is malformed | Verify the JWT structure |
jwt_invalid_signature | 401 | JWT signature verification failed | Ensure the JWT was issued by the correct provider |
jwt_invalid_issuer | 401 | JWT issuer doesn't match expected value | Check the OIDC issuer config on the publishable key |
jwt_invalid_audience | 401 | JWT audience doesn't match | Check the OIDC audience config on the publishable key |
user_id_claim_not_found | 401 | JWT missing the user ID claim | Ensure your JWT includes the sub claim (or the configured userIdClaim) |
Server Errors
| Error | HTTP Status | Cause | Fix |
|---|---|---|---|
unkey_error | 500 | Internal key verification error | Retry the request; if persistent, contact support |
misconfigured_publishable_key | 500 | Publishable key missing OIDC metadata | Recreate the key with publishableConfig.oidc |
oidc_not_configured | 500 | OIDC validator not initialized | Contact platform support |
Quick Reference
Headers
| Header | Required | Description |
|---|---|---|
X-API-Key | Yes | Your API key (sk_live_*, sk_test_*, pk_live_*, or pk_test_*) |
Content-Type | Yes | Always application/json |
Authorization | For pk_* keys | Bearer <user-jwt> — required with publishable keys |
X-On-Behalf-Of | Optional | User ID to impersonate (requires users:impersonate scope) |
Request Format
All API endpoints are accessed through the KrakenD API gateway:
POST /api/v1/<domain>/<service>/<method>
Content-Type: application/json
Request and response bodies use protojson encoding (JSON representation of Protocol Buffer messages).