Skip to main content

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 TypePrefixUse Case
Secret keysk_live_* / sk_test_*Server-side integration from your backend
Publishable keypk_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).

warning

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"
}
}
FieldDescription
key_idUnique identifier — use this for all management operations
keyThe full key value — returned only once, store it securely
key_prefixFirst 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:

FieldDescription
issuerExpected iss claim in the JWT
jwksUrlURL to fetch the public keys for JWT signature verification
audienceExpected aud claim in the JWT
userIdClaim(Optional) JWT claim to extract the user ID from. Defaults to sub
tip

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

ScopeDescription
*Full access (all scopes)
conversations:readRead conversations and threads
conversations:writeSend messages, create threads
plans:readRead coaching plans
plans:writeCreate and modify coaching plans
users:readRead user profiles
users:impersonateAct on behalf of users via X-On-Behalf-Of
billing:readRead billing information
billing:writeModify billing settings
invoices:readRead invoices
invoices:writeModify invoices

Scope Matching Rules

  • Exact match: conversations:read grants 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 keys
  • webhooks:* — Cannot manage webhooks
  • tenants:* — Cannot manage tenants
  • billing:* — Cannot access billing
  • system:* — Cannot access system operations
  • users: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
}
}
FieldDescription
requests_per_minuteMaximum requests per minute (0 = unlimited)
requests_per_hourMaximum requests per hour (0 = unlimited)
burst_sizeMaximum 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"
}
}
FieldDescription
limitMaximum requests in the current window
remainingRequests remaining in the current window
reset_atWhen 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

  1. 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"]
}'
  1. Update all consumers to use the new key. Both old and new keys work during this transition window.

  2. 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-03 for easy tracking
  • Monitor usage — check usage stats before revoking to confirm the old key is no longer in use
  • Set expiration dates — use expires_at to 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

ErrorHTTP StatusCauseFix
missing_credentials401No API key or JWT in requestAdd X-API-Key header
api_key_not_found401Key doesn't existVerify the key value is correct
api_key_expired401Key has passed its expiration dateCreate a new key
api_key_disabled401Key is disabledContact your admin to re-enable
api_key_revoked401Key has been revokedCreate a new key — revocation is permanent
api_key_invalid401Generic invalid keyVerify the key format (sk_live_* or sk_test_*)
insufficient_scope403Key lacks the required scopeUpdate the key's permissions or create a new key with the needed scope
rate_limited429Key hit its rate limitWait for the reset window or increase the rate limit

Publishable Key Errors

ErrorHTTP StatusCauseFix
publishable_key_requires_jwt401pk_* key used without a user JWTAdd Authorization: Bearer <jwt> header
jwt_expired401User JWT has expiredRefresh the JWT token
jwt_malformed401JWT is malformedVerify the JWT structure
jwt_invalid_signature401JWT signature verification failedEnsure the JWT was issued by the correct provider
jwt_invalid_issuer401JWT issuer doesn't match expected valueCheck the OIDC issuer config on the publishable key
jwt_invalid_audience401JWT audience doesn't matchCheck the OIDC audience config on the publishable key
user_id_claim_not_found401JWT missing the user ID claimEnsure your JWT includes the sub claim (or the configured userIdClaim)

Server Errors

ErrorHTTP StatusCauseFix
unkey_error500Internal key verification errorRetry the request; if persistent, contact support
misconfigured_publishable_key500Publishable key missing OIDC metadataRecreate the key with publishableConfig.oidc
oidc_not_configured500OIDC validator not initializedContact platform support

Quick Reference

Headers

HeaderRequiredDescription
X-API-KeyYesYour API key (sk_live_*, sk_test_*, pk_live_*, or pk_test_*)
Content-TypeYesAlways application/json
AuthorizationFor pk_* keysBearer <user-jwt> — required with publishable keys
X-On-Behalf-OfOptionalUser 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).