API Reference
Complete reference for Scrawn SDK and gRPC API
SDK API
New : Tags for pricing. Use debitTag for backend price lookup instead of passing a raw amount.
Constructor
new Scrawn(config: { apiKey: string; baseURL: string })Creates a new Scrawn SDK instance.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
apiKey | string as `scrn_${string}` | Yes | Your Scrawn API key (must start with scrn_) |
baseURL | string | Yes | Backend server URL |
Example:
const scrawn = new Scrawn({
apiKey: process.env.SCRAWN_KEY as `scrn_${string}`,
baseURL: process.env.SCRAWN_BASE_URL || 'http://localhost:8069',
});sdkCallEventConsumer
scrawn.sdkCallEventConsumer(payload: EventPayload): Promise<void>Track billable usage events.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
userId | string | Yes | Unique user identifier |
debitAmount | number | No | Amount to debit from user's balance |
debitTag | string | No | Price tag configured in your backend |
Returns: Promise<void>
Example:
await scrawn.sdkCallEventConsumer({
userId: 'user-123',
debitAmount: 100,
});
await scrawn.sdkCallEventConsumer({
userId: 'user-123',
debitTag: 'STANDARD_API_CALL',
});Validation:
userIdmust be a non-empty string- Either
debitAmountordebitTagmust be provided debitAmountmust be a positive numberdebitTagmust be a non-empty string
Throws:
Errorwith detailed validation message on invalid payloadErroron gRPC call failure
middlewareEventConsumer
scrawn.middlewareEventConsumer(config: MiddlewareEventConfig):
(req: MiddlewareRequest, res: MiddlewareResponse, next: MiddlewareNext) => Promise<void>Creates Express-compatible middleware that automatically tracks usage for API requests.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
extractor | PayloadExtractor | Yes | Function to extract userId and debit info from request |
whitelist | string[] | No | Array of endpoint patterns to track (takes precedence over blacklist) |
blacklist | string[] | No | Array of endpoint patterns to exclude from tracking |
Types:
type PayloadExtractor = (req: MiddlewareRequest) =>
EventPayload | Promise<EventPayload> | null | Promise<null>;
interface MiddlewareEventConfig {
extractor: PayloadExtractor;
whitelist?: string[];
blacklist?: string[];
}Returns: Express-compatible middleware function (req, res, next) => Promise<void>
Example:
app.use(scrawn.middlewareEventConsumer({
extractor: (req): EventPayload => ({
userId: req.headers['x-user-id'] as string,
debitAmount: req.body?.cost || 1,
}),
blacklist: ['/health', '/api/collect-payment'],
}));Wildcard Patterns:
*matches single path segment (e.g.,/api/*matches/api/usersbut not/api/users/123)**matches multiple segments (e.g.,/api/**matches any path starting with/api/)
Error Handling:
- Errors are automatically caught and logged without blocking requests
- Failed tracking attempts don't affect the HTTP response
collectPayment
scrawn.collectPayment(userId: string): Promise<string>Generate payment checkout link for a user to pay their outstanding amount.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
userId | string | Yes | Unique identifier of the user to collect payment from |
Returns: Promise<string> - Checkout URL where the user can complete payment
Example:
const checkoutLink = await scrawn.collectPayment('user-123');
// Redirect user to payment page
res.redirect(checkoutLink);Validation:
userIdmust be a non-empty string
Throws:
ErrorifuserIdis invalidErroron gRPC call failure
gRPC API
Scrawn backend exposes three gRPC services for direct integration.
EventService
registerEvent
Registers a billable SDK call event.
Request:
message RegisterEventRequest {
EventType type = 1;
string userId = 2;
oneof data {
SDKCall sdkCall = 3;
}
}
message SDKCall {
SDKCallType sdkCallType = 1;
oneof debit {
float amount = 2;
string tag = 3;
}
}
enum EventType {
EVENT_TYPE_UNSPECIFIED = 0;
SDK_CALL = 1;
}
enum SDKCallType {
SDKCallType_UNSPECIFIED = 0;
RAW = 1;
MIDDLEWARE_CALL = 2;
}Response:
message RegisterEventResponse {
string random = 1;
}Example (TypeScript):
import { createPromiseClient } from '@connectrpc/connect';
import { EventService } from './gen/event/v1/event_connect';
import { EventType, SDKCallType } from './gen/event/v1/event_pb';
const client = createPromiseClient(EventService, transport);
const response = await client.registerEvent({
type: EventType.SDK_CALL,
userId: 'user_123',
data: {
case: 'sdkCall',
value: {
sdkCallType: SDKCallType.RAW,
debit: {
case: 'amount',
value: 100,
},
}
}
});
console.log('Status:', response.random);Authentication:
- Requires valid API key in
Authorizationheader as Bearer token - API key must start with
scrn_
AuthService
createAPIKey
Creates a new API key for authenticated users.
Request:
message CreateAPIKeyRequest {
string name = 1;
int64 expiresIn = 2; // expiration time in seconds from now
}Response:
message CreateAPIKeyResponse {
string apiKeyId = 1;
string apiKey = 2;
string name = 3;
string createdAt = 4;
string expiresAt = 5;
}Example:
import { createPromiseClient } from '@connectrpc/connect';
import { AuthService } from './gen/auth/v1/auth_connect';
const client = createPromiseClient(AuthService, transport);
const response = await client.createAPIKey({
name: 'Production Key',
expiresIn: BigInt(365 * 24 * 60 * 60) // 1 year in seconds
});
console.log('API Key:', response.apiKey);
console.log('API Key ID:', response.apiKeyId);
console.log('Expires At:', response.expiresAt);Authentication:
- Requires valid API key in
Authorizationheader as Bearer token - New API key will be generated with
scrn_prefix
Notes:
- The API key is returned only once during creation
- Store the API key securely - it cannot be retrieved later
- The backend stores a hashed version of the API key
PaymentService
createCheckoutLink
Generates a Lemon Squeezy checkout link for a user to pay their outstanding balance.
Request:
message CreateCheckoutLinkRequest {
string userId = 1;
}Response:
message CreateCheckoutLinkResponse {
string checkoutLink = 1;
}Example:
import { createPromiseClient } from '@connectrpc/connect';
import { PaymentService } from './gen/payment/v1/payment_connect';
const client = createPromiseClient(PaymentService, transport);
const response = await client.createCheckoutLink({
userId: 'user_123'
});
console.log('Checkout Link:', response.checkoutLink);
// Redirect user to this URL to complete paymentHow it works:
- The backend calculates the user's outstanding balance automatically
- A custom-priced checkout session is created via Lemon Squeezy
- The checkout link is valid for immediate use
- User ID is passed to the payment provider for tracking
Notes:
- Payment amount is calculated from stored debit events
- Uses Lemon Squeezy as the payment provider
- No need to specify amount - it's calculated server-side
Error Handling
SDK Errors
The SDK throws standard JavaScript Error objects with descriptive messages.
Validation Errors:
try {
await scrawn.sdkCallEventConsumer({
userId: '', // Invalid: empty string
debitAmount: -5, // Invalid: negative number
});
} catch (error) {
console.error(error.message);
// "Payload validation failed: userId: must be a non-empty string, debitAmount: must be a positive number"
}gRPC Connection Errors:
try {
await scrawn.sdkCallEventConsumer({
userId: 'user-123',
debitAmount: 100,
});
} catch (error) {
console.error('Failed to register event:', error.message);
}Backend Error Typesuctured errors using gRPC Connect error codes.
AuthError
Authentication and authorization errors.
Error Types:
| Type | Code | Description | Solution |
|---|---|---|---|
MISSING_HEADER | UNAUTHENTICATED | No Authorization header provided | Include Authorization: Bearer <api_key> header |
INVALID_HEADER_FORMAT | UNAUTHENTICATED | Authorization header format is wrong | Use format: Bearer scrn_... |
INVALID_API_KEY | UNAUTHENTICATED | API key is invalid or not found | Check API key is correct and exists |
EXPIRED_API_KEY | UNAUTHENTICATED | API key has expired | Create a new API key |
REVOKED_API_KEY | UNAUTHENTICATED | API key has been revoked | Create a new API key |
DATABASE_ERROR | INTERNAL | Failed to verify API key | Contact support |
UNKNOWN | INTERNAL | Unknown authentication error | Contact support |
EventError
Event registration and processing errors.
Error Types:
| Type | Code | Description | Solution |
|---|---|---|---|
INVALID_PAYLOAD | INVALID_ARGUMENT | Event payload is invalid | Check payload structure matches schema |
UNSUPPORTED_EVENT_TYPE | INVALID_ARGUMENT | Event type is not supported | Use SDK_CALL event type |
VALIDATION_FAILED | INVALID_ARGUMENT | Zod schema validation failed | Review validation error details |
INVALID_USER_ID | INVALID_ARGUMENT | User ID format is invalid | Provide non-empty string for userId |
MISSING_DATA | INVALID_ARGUMENT | Required field is missing | Include all required fields |
INVALID_DATA_FORMAT | INVALID_ARGUMENT | Data format doesn't match expected | Check field types and formats |
SERIALIZATION_ERROR | INTERNAL | Failed to store event | Contact support |
UNKNOWN | INTERNAL | Unknown event processing error | Contact support |
PaymentError
Payment and checkout link creation errors.
Error Types:
| Type | Code | Description | Solution |
|---|---|---|---|
INVALID_USER_ID | INVALID_ARGUMENT | User ID is invalid | Provide valid non-empty userId |
VALIDATION_FAILED | INVALID_ARGUMENT | Request validation failed | Check request parameters |
MISSING_API_KEY | FAILED_PRECONDITION | Lemon Squeezy API key not configured | Configure LEMON_SQUEEZY_API_KEY env var |
MISSING_STORE_ID | FAILED_PRECONDITION | Lemon Squeezy store ID not configured | Configure LEMON_SQUEEZY_STORE_ID env var |
MISSING_VARIANT_ID | FAILED_PRECONDITION | Lemon Squeezy variant ID not configured | Configure LEMON_SQUEEZY_VARIANT_ID env var |
PRICE_CALCULATION_FAILED | INTERNAL | Failed to calculate user's balance | Ensure user has registered events |
STORAGE_ADAPTER_FAILED | INTERNAL | Failed to retrieve data from storage | Check database connectivity |
LEMON_SQUEEZY_API_ERROR | INTERNAL | Lemon Squeezy API call failed | Check Lemon Squeezy service status |
INVALID_CHECKOUT_RESPONSE | INTERNAL | Invalid response from payment provider | Contact support |
CHECKOUT_CREATION_FAILED | INTERNAL | Failed to create checkout link | Review error details |
CONFIGURATION_ERROR | FAILED_PRECONDITION | Payment system misconfigured | Check environment configuration |
UNKNOWN | INTERNAL | Unknown payment error | Contact support |
Error Response Format
All backend errors follow the gRPC Connect error format:
{
"code": "invalid_argument",
"message": "Event validation failed: userId: must be a non-empty string",
"details": []
}Common gRPC Codes:
INVALID_ARGUMENT- Invalid request parametersUNAUTHENTICATED- Authentication failedINTERNAL- Internal server errorFAILED_PRECONDITION- Server not properly configured
Types
EventPayload
interface EventPayload {
userId: string;
debitAmount?: number;
debitTag?: string;
}Validated using Zod schema:
userId: Must be a non-empty string- Exactly one of
debitAmountordebitTagmust be provided debitAmount: Must be a positive numberdebitTag: Must be a non-empty string
MiddlewareRequest
interface MiddlewareRequest {
method?: string;
url?: string;
path?: string;
body?: any;
headers?: Record<string, string | string[] | undefined>;
query?: Record<string, any>;
params?: Record<string, any>;
[key: string]: any;
}Generic request object compatible with Express, Fastify, and other Node.js frameworks.
MiddlewareResponse
interface MiddlewareResponse {
status?: (code: number) => MiddlewareResponse;
json?: (data: any) => void;
send?: (data: any) => void;
[key: string]: any;
}Generic response object compatible with Express, Fastify, and other Node.js frameworks.
MiddlewareNext
type MiddlewareNext = (error?: any) => void;PayloadExtractor
type PayloadExtractor = (
req: MiddlewareRequest
) => EventPayload | Promise<EventPayload> | null | Promise<null>;Function that extracts userId and either debitAmount or debitTag from a request. Return null to skip tracking.
MiddlewareEventConfig
interface MiddlewareEventConfig {
extractor: PayloadExtractor;
whitelist?: string[];
blacklist?: string[];
}Configuration for middlewareEventConsumer.
Utilities
matchPath
matchPath(path: string, pattern: string): booleanMatches a path against a pattern with wildcard support.
Parameters:
| Parameter | Type | Description |
|---|---|---|
path | string | The path to match |
pattern | string | The pattern with optional wildcards |
Wildcard Patterns:
*matches single path segment**matches multiple segments
Example:
import { matchPath } from '@scrawn/core';
matchPath('/api/users', '/api/*'); // true
matchPath('/api/users/123', '/api/*'); // false
matchPath('/api/users/123', '/api/**'); // true
matchPath('/api/v1/users', '/api/v1/*'); // trueAuthentication
All API requests require authentication via Bearer token in the Authorization header.
API Key Format:
- Must start with
scrn_prefix - Example:
scrn_1234567890abcdef
Using the SDK:
The SDK handles authentication automatically using the apiKey provided in the constructor:
const scrawn = new Scrawn({
apiKey: process.env.SCRAWN_KEY as `scrn_${string}`,
baseURL: 'http://localhost:8069',
});Direct gRPC Calls:
When making direct gRPC calls without the SDK, include the API key in the Authorization header:
Authorization: Bearer scrn_your_api_keyAuthentication Flow:
- Backend receives request with Bearer token
- Auth interceptor validates the API key format
- API key hash is verified against database
- API key expiration is checked
- API key ID is added to request context for downstream handlers
Security Notes:
- API keys are hashed using SHA-256 before storage
- Original API keys are never stored in the database
- API keys are cached in memory for performance (with TTL)
- Expired API keys are automatically rejected