A quick lookup of everything hanging off context (Node) / Context (C#). For the conceptual tour, see context object. For method-level detail, trust TypeScript IntelliSense or dotnet's IntelliSense — this page is the "what can I type" reference.
| Capability | Node.js | C# |
|---|---|---|
| Key-value cache | context.cache | Context.Cache |
| Blob / file storage | context.storage | Context.Storage |
| SQLite database | context.database (path) | Context.Database.ConnectionString |
| Structured logger | console | Context.Logger |
| Project + function info | context.config | Context.Configuration |
| Env vars | context.env / process.env | Context.Configuration |
| Request ID | context.requestId | HttpContext.TraceIdentifier |
| Timestamp | context.timestamp | (see request headers) |
| Voice SDK client | context.voice | Context.Voice |
| Conversation SDK | context.conversation | Context.Conversation |
| SMS SDK | context.sms | Context.Sms |
| Numbers SDK | context.numbers | Context.Numbers |
| Verification SDK (C#) | — | Context.Verification |
| Private assets | context.assets(filename) | (package helpers) |
Except for context.voice, which is always present, SDK client properties are undefined (Node.js) or null (C#) if the required environment variables are missing, so check them before use. See SDK env vars for which variables unlock each client.
// Node
await context.cache.set('session:abc', { userId: 'u1' }, 1800); // TTL seconds
const session = await context.cache.get<{ userId: string }>('session:abc');
const exists = await context.cache.has('session:abc');
await context.cache.extend('session:abc', 600);
await context.cache.delete('session:abc');
const keys = await context.cache.keys('session:*');
const many = await context.cache.getMany<Session>(keys);// C#
await Context.Cache.Set("session:abc", new { UserId = "u1" }, 1800);
var session = await Context.Cache.Get<MySession>("session:abc");
if (await Context.Cache.Exists("session:abc"))
await Context.Cache.Delete("session:abc");
var keys = await Context.Cache.GetKeys();Default TTL is 3600 seconds. Values are JSON-serialized.
// Node
await context.storage.write('reports/daily.json', JSON.stringify(data));
const buf = await context.storage.read('reports/daily.json');
const files = await context.storage.list('reports/');
const exists = await context.storage.exists('reports/old.json');
await context.storage.delete('reports/old.json');// C#
await Context.Storage.WriteAsync("reports/daily.json", JsonSerializer.Serialize(data));
var text = await Context.Storage.ReadTextAsync("reports/daily.json");
var bytes = await Context.Storage.ReadAsync("reports/daily.json");
var files = await Context.Storage.ListAsync("reports/");
// Streams for large files
using var stream = await Context.Storage.ReadStreamAsync("large-file.bin");Keys can include / — treat them as folder paths.
// Node — context.database is a file path
import initSqlJs from 'sql.js';
import { readFileSync, writeFileSync, existsSync } from 'fs';
const SQL = await initSqlJs();
const buf = existsSync(context.database) ? readFileSync(context.database) : undefined;
const db = new SQL.Database(buf);
db.run('CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value TEXT)');
writeFileSync(context.database, Buffer.from(db.export()));// C# — Context.Database.ConnectionString is an ADO.NET connection string
using var conn = new SqliteConnection(Context.Database.ConnectionString);
conn.Open();
using var cmd = conn.CreateCommand();
cmd.CommandText = "CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value TEXT)";
cmd.ExecuteNonQuery();In production, the platform automatically replicates and backs up the database. No code changes required.
// Node
console.log('Request received', { callId: data.callid });// C#
Logger.LogInformation("Request received from {Cli}", data.Cli);Output is captured and streamed via sinch functions logs.
// Node — raw
context.config.projectId;
context.config.functionName;
context.config.environment; // 'development' | 'production'
context.config.variables; // all environment variables (same object as context.env)
// Node — UniversalConfig helper
import { createConfig } from '@sinch/functions-runtime';
const config = createConfig(context);
config.requireSecret('STRIPE_SECRET_KEY'); // throws if missing
config.getVariable('COMPANY_NAME', 'Acme Corp');
config.getApplicationCredentials(); // voice app key/secret
config.isDevelopment();
config.isProduction();// C#
var companyName = Configuration["COMPANY_NAME"] ?? "Acme Corp";
var apiKey = Configuration["STRIPE_SECRET_KEY"]
?? throw new InvalidOperationException("STRIPE_SECRET_KEY is required");See configuration & secrets for how the layers fit together.
Initialized by the runtime when the required env vars are present. The exception is context.voice, the Voice API v2 client, which is always present; the Voice API v1 client is context.voice.v1. See SDK env vars for the full list.
// Node — always null-check
if (context.voice.v1) {
await context.voice.v1.callouts.custom({ ... });
}
if (context.conversation) {
await context.conversation.messages.send({ ... });
}
if (context.sms) {
await context.sms.batches.send({ ... });
}// C# — same pattern
if (Context.Voice.V1 is not null)
await Context.Voice.V1.Callouts.Custom(...);
if (Context.Conversation is not null)
await Context.Conversation.Messages.Send(...);Private files you ship alongside your code. Do not use fs.readFileSync('./assets/...') — the deployed artifact structure differs from dev.
// Node
const greeting = await context.assets('greetings/en.txt');
const audioBuffer = await context.assets('prompts/welcome.wav');// C# — context.Assets helper
var greeting = await Context.Assets("greetings/en.txt");For public static files (images, CSS), use the public/ directory — the runtime serves it at /.
// Node
context.requestId; // tracing ID unique to this invocation
context.timestamp; // ISO 8601 when the request entered the runtimeInclude context.requestId in logs and upstream calls for cross-service debugging.
For non-voice custom endpoints, the second argument is a FunctionRequest:
interface FunctionRequest {
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
path: string;
query?: Record<string, string | string[]>;
headers?: Record<string, string>;
body?: unknown;
params?: Record<string, string>;
}And you return a plain { statusCode, body, headers? } object:
return { statusCode: 200, body: { status: 'healthy' } };
return { statusCode: 400, body: { error: 'Missing field: name' } };
return { statusCode: 404, body: { error: 'Not found' } };C# custom endpoints use regular ASP.NET MVC action results (Ok, NotFound, BadRequest, StatusCode, etc.).
- Context object concept — narrative tour of the same APIs
- SDK env vars — which variables unlock which client
- Configuration & secrets — how config flows into the runtime
- Node.js runtime / C# runtime — runtime guides



