This page is generated from the platform's own SDK reference, so it lists exactly what the Python and JavaScript sandboxes provide. It is the complete, call-by-call reference; [The SDK & security model](glyphs/the-sdk) explains why ctx exists and what it keeps you from doing, and [Standard-library reference](glyphs/standard-library) has a worked example for every ctx.lib function. Every member is scoped to your org and tenant automatically.

Data: ctx.app_db

Get one entity. Reads one record by the entity's own business name + record id — goes through the real entity step-runner, so whatever View/Projection that entity already has is honored, not bypassed.

python
# ctx.app_db.get_entity(entity_name, entity_id)
product = ctx.app_db.get_entity('product', request['id'])
javascript
// await ctx.app_db.getEntity(entityName, entityId)
const product = await ctx.app_db.getEntity('product', request.id);

List entities. filters is a list of {field, op, value}, implicitly ANDed — same shape Logic Block/connector steps already use. sort_by/sortBy is 'name' or 'updated_at' (each uses its own index across all records), or 'id'/'created_at' (the default creation order); sort_dir/sortDir is 'asc' or 'desc'. A page holds at most 500 records.

python
# ctx.app_db.list_entities(entity_name, filters=None, limit=None, next_token=None, sort_by=None, sort_dir=None)
page = ctx.app_db.list_entities('product', filters=[{'field':'price','op':'lt','value':20}], limit=10, sort_by='updated_at', sort_dir='desc')
javascript
// await ctx.app_db.listEntities(entityName, filters?, limit?, nextToken?, sortBy?, sortDir?)
const page = await ctx.app_db.listEntities('product', [{field:'price',op:'lt',value:20}], 10, undefined, 'updated_at', 'desc');

Watch out

The result's own next_token/nextToken pages to more results when passed back in on the next call — absent means no more pages. A filter on a field that is not indexed reads at most about 2000 records per call; when the result has scanLimited: true a short (even empty) page is NOT the end — keep following nextToken until it is absent. Prefer an indexed filter, or sort_by='name' to walk a whole entity in order.

Create one entity. Real id generation, uniqueness checks, and index writes happen for real — same as any other create.

python
# ctx.app_db.create_entity(entity_name, fields)
new_row = ctx.app_db.create_entity('product', {'title': 'Mug', 'price': 12})
javascript
// await ctx.app_db.createEntity(entityName, fields)
const newRow = await ctx.app_db.createEntity('product', { title: 'Mug', price: 12 });

Update one entity. Partial update — only the fields you pass are changed.

python
# ctx.app_db.update_entity(entity_name, entity_id, fields)
ctx.app_db.update_entity('product', request['id'], {'price': 10})
javascript
// await ctx.app_db.updateEntity(entityName, entityId, fields)
await ctx.app_db.updateEntity('product', request.id, { price: 10 });

Delete one entity. Hard-deletes the record (not a soft delete) through the real delete step-runner.

python
# ctx.app_db.delete_entity(entity_name, entity_id)
ctx.app_db.delete_entity('product', request['id'])
javascript
// await ctx.app_db.deleteEntity(entityName, entityId)
await ctx.app_db.deleteEntity('product', request.id);

Batch-create entities. A concurrency-limited loop of real single creates, not a raw DynamoDB batch call. No batch update/delete exists yet on the platform.

python
# ctx.app_db.batch_create_entities(entity_name, items)
ctx.app_db.batch_create_entities('product', [{'title':'A'},{'title':'B'}])
javascript
// await ctx.app_db.batchCreateEntities(entityName, items)
await ctx.app_db.batchCreateEntities('product', [{title:'A'},{title:'B'}]);

Files: ctx.content_s3

Read a blob. key is relative — automatically prefixed to this org+tenant's own content path. Not routed through glyph-facade's entity logic, since there's no business logic to reuse for a raw blob.

python
# ctx.content_s3.get_object(key)
pdf_bytes = ctx.content_s3.get_object('invoices/inv-1.pdf')
javascript
// await ctx.content_s3.getObject(key)
const pdfBytes = await ctx.content_s3.getObject('invoices/inv-1.pdf');

Write a blob. Same automatic org+tenant key prefixing as get_object/getObject.

python
# ctx.content_s3.put_object(key, body, content_type=None)
ctx.content_s3.put_object('receipts/r-1.txt', 'Thanks!', 'text/plain')
javascript
// await ctx.content_s3.putObject(key, body, contentType?)
await ctx.content_s3.putObject('receipts/r-1.txt', 'Thanks!', 'text/plain');

Outbound HTTP: ctx.http

HTTP GET. Raises/rejects if url's host isn't in this org's Allowed Domains — the same SSRF guard connector calls already enforce.

python
# ctx.http.get(url)
resp = ctx.http.get('https://api.example.com/rates')
javascript
// await ctx.http.get(url)
const resp = await ctx.http.get('https://api.example.com/rates');

Watch out

An org with an EMPTY Allowed Domains list blocks every ctx.http call, not "allow everything" — add at least one domain under Org Settings first.

HTTP POST. Same Allowed Domains allowlist check as ctx.http.get.

python
# ctx.http.post(url, body=None, headers=None)
ctx.http.post('https://api.example.com/webhook', {'event': 'done'})
javascript
// await ctx.http.post(url, body?, headers?)
await ctx.http.post('https://api.example.com/webhook', { event: 'done' });

Events and async: ctx.eventbridge, ctx.queue

Publish a custom event. Fires under the reserved nostackai.glyph source (never nostackai.app) — can trigger a real, org-configured Automation for that event type.

python
# ctx.eventbridge.publish(event_type, detail=None)
ctx.eventbridge.publish('order_ready_for_pickup', {'order_id': request['id']})
javascript
// await ctx.eventbridge.publish(eventType, detail?)
await ctx.eventbridge.publish('order_ready_for_pickup', { orderId: request.id });

Re-invoke this same Glyph asynchronously. Not a generic queue — re-runs THIS SAME Glyph later with payload as its new request. Defaults to re-running the same version (draft/active) this invocation itself ran as. Status visible via the job monitor.

python
# ctx.queue.reinvoke(payload, delay_seconds=None)
ctx.queue.reinvoke({'step': 2}, delay_seconds=30)
javascript
// await ctx.queue.reinvoke(payload, delaySeconds?)
await ctx.queue.reinvoke({ step: 2 }, 30);

Email: ctx.ses

Send an email. Always sent from the org's own real, verified identity — a Glyph-supplied 'from' is never honored.

python
# ctx.ses.send(to, subject, body, cc=None, bcc=None, format=None)
ctx.ses.send(['customer@example.com'], 'Your order shipped', 'Thanks for your order!')
javascript
// await ctx.ses.send(to, subject, body, options?)
await ctx.ses.send(['customer@example.com'], 'Your order shipped', 'Thanks for your order!');

Connectors: ctx.connector

Run a connector action. Runs an action of a connector your org has adopted (a payment checkout, a Slack message, ...) through the same code the /ipaas route, Logic Blocks, schedules and agents use: the tenant's own credentials, your org's field overlay, the audit record and the webhook correlation all behave as they do there. Returns { result, raw }: the action's returned fields and the provider's raw response. It runs as the user this run is for (ctx.user).

python
# ctx.connector.invoke(connector_id, action_id, fields=None, entity_ref=None)
out = ctx.connector.invoke('paypal', 'checkout_cart', {'line_items': items, 'currency_code': 'USD', 'total': '21.48'}, entity_ref=order['id'])
url = out['result']['checkout_url']
javascript
// await ctx.connector.invoke(connectorId, actionId, fields?, entityRef?)
const out = await ctx.connector.invoke('paypal', 'checkout_cart', { line_items: items, currency_code: 'USD', total: '21.48' }, order.id);
const url = out.result.checkout_url;

Watch out

Fails with a named error when the connector or action is unknown, the org has not adopted the connector, or the tenant has not configured it. The prices and amounts you pass are trusted as given, so build them from your own records, not from the request.

Standard library: ctx.lib

A platform-curated allowlist of pure functions, not a general import: raw import and require stay blocked. Each library exposes only the members listed. A blank cell means the library does not exist in that language.

LibraryPythonNodeSource
ctx.lib.jsonctx.lib.json.{dumps, loads, JSONDecodeError}Built in
ctx.lib.rectx.lib.re.{search, match, fullmatch, findall, finditer, sub, split, compile, escape, IGNORECASE, MULTILINE, DOTALL}Built in
ctx.lib.mathctx.lib.math (whole module)Built in
ctx.lib.statisticsctx.lib.statistics (whole module)Built in
ctx.lib.itertoolsctx.lib.itertools (whole module)Built in
ctx.lib.collectionsctx.lib.collections.{Counter, defaultdict, namedtuple, OrderedDict, deque}Built in
ctx.lib.datetimectx.lib.datetime.{datetime, date, time, timedelta, timezone}Built in
ctx.lib.decimalctx.lib.decimal.{Decimal, getcontext, setcontext, localcontext}Built in
ctx.lib.uuidctx.lib.uuid.{uuid4}ctx.lib.uuid.{v4}Built in
ctx.lib.cryptoctx.lib.crypto.{randomUUID, createHash, createHmac, randomBytes, randomInt, timingSafeEqual}Built in
ctx.lib.utilctx.lib.util.{format, inspect}Built in
ctx.lib.querystringctx.lib.querystring.{parse, stringify}Built in
ctx.lib.pathctx.lib.path (whole module)Built in
ctx.lib.urlctx.lib.url.{URL}Built in
ctx.lib.functoolsctx.lib.functools.{reduce, partial, lru_cache, cmp_to_key}Built in
ctx.lib.operatorctx.lib.operator.{itemgetter, attrgetter, methodcaller, add, sub, mul, truediv, and_, or_, not_}Built in
ctx.lib.textwrapctx.lib.textwrap.{wrap, fill, shorten, dedent, indent}Built in
ctx.lib.difflibctx.lib.difflib.{SequenceMatcher, unified_diff, get_close_matches}Built in
ctx.lib.bisectctx.lib.bisect (whole module)Built in
ctx.lib.heapqctx.lib.heapq (whole module)Built in
ctx.lib.copyctx.lib.copy.{copy, deepcopy}Built in
ctx.lib.secretsctx.lib.secrets.{token_hex, token_urlsafe, token_bytes, compare_digest}Built in
ctx.lib.hashlibctx.lib.hashlib.{sha256, sha1, sha512, sha384, md5, blake2b, blake2s, new, pbkdf2_hmac, algorithms_available}Built in
ctx.lib.hmacctx.lib.hmac.{new, digest, compare_digest}Built in
ctx.lib.randomctx.lib.random.{Random}ctx.lib.random.{Random}Built in
ctx.lib.stringctx.lib.string.{ascii_letters, ascii_lowercase, ascii_uppercase, digits, punctuation, Template}Built in
ctx.lib.dateutil_parserctx.lib.dateutil_parser.{parse}Vetted package
ctx.lib.dateutil_relativedeltactx.lib.dateutil_relativedelta.{relativedelta}Vetted package
ctx.lib.assertctx.lib.assert.{ok, strictEqual, deepStrictEqual, notStrictEqual, match, throws, AssertionError}Built in
ctx.lib.bufferctx.lib.buffer.{from, alloc, concat, compare, isBuffer}Built in
ctx.lib.structuredClonectx.lib.structuredClone.{clone}Built in
ctx.lib.lodashctx.lib.lodash.{get, set, merge, isEqual, cloneDeep, pick, omit, sortBy, uniqBy, chunk}Vetted package

Worked examples for each are in the [Standard-library reference](glyphs/standard-library).

Context and logging

Org code (read-only). Platform-provided, always present — not something a Glyph declares or configures.

python
# ctx.org_code
org = ctx.org_code
javascript
// ctx.orgCode
const org = ctx.orgCode;

Tenant code (read-only). None/null for an org-level (no-tenant) invocation.

python
# ctx.tenant_code
tenant = ctx.tenant_code
javascript
// ctx.tenantCode
const tenant = ctx.tenantCode;

The invoking user (read-only). The person this run is for: sub, email and name (the last two are None/null when not known). Set for a direct call, a Logic Block step, an agent, a schedule (the user it runs as), and a re-invoked run keeps it. Null when nobody is known.

python
# ctx.user
who = ctx.user.sub if ctx.user else None
email = ctx.user.email if ctx.user else None
javascript
// ctx.user
const who = ctx.user?.sub;
const email = ctx.user?.email;

Watch out

Read-only and informational: the platform acts on the user from the signed scope, not from this value, so changing it changes nothing outside your code.

This Glyph's own settings. The author-set key/value pairs from the Settings tab's Environment config editor.

python
# ctx.config
api_key = ctx.config.get('api_key')
javascript
// ctx.config
const apiKey = ctx.config.apiKey;

The tenant's settings and secrets (read-only). The tenant's own Tenant Management → General settings and Secrets, decrypted for this run. Different from ctx.config, which is the Glyph author's own settings. Use a secret to call something; every secret value is scrubbed from the Glyph's result, logs and errors.

python
# ctx.params.settings / ctx.params.secrets
region = ctx.params.settings.get('region')
api_key = ctx.params.secrets['API_KEY']
javascript
// ctx.params.settings / ctx.params.secrets
const region = ctx.params.settings.region;
const apiKey = ctx.params.secrets.API_KEY;

Logging. Captured and returned as logs in the invoke result (Test tab / job monitor). print/console.log are also captured, but ctx.log is the documented, stable API.

python
# ctx.log(*args)
ctx.log('processing order', request['id'])
javascript
// ctx.log(...args)
ctx.log('processing order', request.id);