---
title: "Execution Contexts"
description: "Identity, inputs, output, side effects, and the right test for each place ZeyOS runs your code."
canonical_url: "https://www.zeyos.com/ixml/ixml-guide/development-environment/execution-contexts"
markdown_url: "https://www.zeyos.com/ai/guide/ixml/ixml-guide/development-environment/execution-contexts.md"
section: "ixml-guide"
authority: "guide"
language: "en"
source_repository: "zeyos/guide"
source_path: "docs/ixml/03-development-environment/execution-contexts.md"
source_revision: "cce886a8da98cd124ee3644711085bbdcf1a87ce"
---

# Execution Contexts

The same iXML file can behave differently depending on how it was started. Identity, available globals, and HTTP behavior all come from the **execution context**, not from the file. Most app bugs that survive compilation are context bugs: code that works in `zeysdk run` fails in a weblet, or a service that works with a token rejects a browser call.

Identify the context first, then read the row that applies. Test in that same context. A pass in one context says little about another.

## How to read the evidence labels

| Label | Meaning |
| --- | --- |
| **Reference** | Stated in the official iXML, SDK, or API reference |
| **Source-confirmed** | Read in the platform or SDK source for this release. It describes behavior at contract level and has not been executed for this page. A later platform release can change it |
| **Logged** | Dated execution result in the guide's validation log |
| **Reported** | Field observation that was not independently verified. Check it in your target |

## Context matrix

| Context | Identity and auth source | Inputs and globals | Output and HTTP | Side effects | How to test it |
| --- | --- | --- | --- | --- | --- |
| **REST API** (external client, for contrast) | Token sent by the client, or the session cookie of a signed-in browser | The documented request only. Your iXML does not run here | JSON as defined by the OpenAPI reference | Record writes can fire entity hooks | HTTP request against a sandbox, or the `zeyos` CLI |
| **Weblet, framed or page** | Browser session cookie. No valid session means no execution | `REQUEST`, `GET`, `POST`, `FILES`, `SERVER`, `SELFURL`, `VIEW`, `SELFID`, `SELFNS`, `APPSETTINGS`. Record context arrives as request parameters such as `$REQUEST.id` | Your output and headers go to the frame | No implicit transaction. Use `db:transaction` | Open the real view while signed in |
| **Weblet in a `.form` view** | Runs while the page is built for the signed-in user | `ENTITY`, `ID`, `DATA`, `EXTDATA`, `TAGS`, `FORM`, `SELFID`, `SELFNS`, `APPSETTINGS`. No `REQUEST`. New-record dialogs differ: no `FORM`, and normally no record variables | Form controls are collected. Plain output is discarded | Form data is saved by the host page | Open the record form and save it |
| **`REMOTECALL` service** | Bearer token of the access or legacy type, read into `AUTH`. The cookie is ignored. A configured access key is a separate branch | `AUTH` when authenticated, `REQUEST`, `GET`, `POST`, `FILES`, `SERVER`, `SELFURL`, `SELFID`, `SELFNS`, `APPSETTINGS`. Inside `rest:resource`: route parameters, plus query arguments or POST form fields as locals, which [can hide these globals](#bind-globals-inside-restresource) | Your output, headers, and status go to the caller. CORS is open | No implicit transaction. Unauthenticated calls can still run, even for a restricted application | HTTP request with a token, without one, with an invalid one, and with forged query arguments |
| **`zeysdk run`** | SDK token of a developer. No end-user session | `SELFID`, `SELFNS`, `APPSETTINGS`, `DEBUG`. No `AUTH`, `REQUEST`, `SELFURL`, or `VIEW` | Output, status, and headers are returned as a report | Real. Writes, mail, and external calls all happen | Only for the code path itself, on a sandbox |
| **`TIMING` service** | Scheduler. No user | `SELFID`, `SELFNS`, `APPSETTINGS` | Discarded. Errors go to the platform log | No implicit transaction. One concurrent run per service | Wait for the schedule and inspect the effects |
| **Entity hook** | Runs inside the operation that changed the record. No `AUTH` | `ENTITY`, `ID`, and [event-specific values](#entity-hooks), plus `SELFID`, `SELFNS`, `APPSETTINGS` | Discarded | Inside the record transaction. An error aborts the operation | Trigger the real entity event |

The globals, auth branches, and output handling in this table are **Source-confirmed**. Asset types and the `REMOTECALL` and `TIMING` metadata are **Reference**.

`APPSETTINGS` is the one input every application context shares. Everything else must be checked per context. The global names above are the iXML forms. Zymba has its own access syntax, so check `ref/zymbadoc.md` for Zymba assets.

## Weblets

A weblet's `type` decides how the browser reaches it. Its `view` decides where it appears. ZeySDK 3.3.0 accepts these types:

| Type | Presentation (**Source-confirmed**) |
| --- | --- |
| `INTEGRATED` | Menu entry that opens a page inside the ZeyOS shell |
| `STANDALONE` | Menu entry that navigates the same tab |
| `DETACHED` | Menu entry that opens a new tab |
| `POPUP_FRAMED`, `POPUP_PLAIN` | Menu entry that opens a dialog with a frame |
| `EMBEDDED_FRAMED`, `EMBEDDED_COLLAPSED`, `EMBEDDED_PLAIN` | Frame inside the view, with or without a collapsible heading |

All of these reach the same weblet endpoint, with one exception: a weblet registered on a view ending in `.form` is not a page. ZeyOS runs it on the server while it builds the form, so it must be iXML and it gets the record variables directly. Choose the path before you write code, because `$REQUEST` and `$ID` are not interchangeable across the two.

The endpoint requires a valid session. Without one it answers 401. An inactive weblet or application answers 403, as does a restricted application the user has no permission for. A weblet with a configured `url` redirects to that URL instead of running code.

`view` controls placement, not access. A weblet's URL is reachable by any signed-in user, regardless of its `view`, unless the application is restricted and the user lacks permission for it.

`$REQUEST.id` is an ordinary query parameter. The session proves who the user is, not that this user may see that record. Check access before you use the value in a query. See [Weblets](https://www.zeyos.com/ixml/ixml-guide/user-interface/weblets#check-record-access) for an example and for the limits on requests that change data.

## REMOTECALL services

The service URL has this form, where `<base>` is your instance URL:

```text
<base>/remotecall/<app>.<service>/<route>
```

`$SELFURL` holds the numeric form of the same address, `<base>/remotecall/~<ID>`.

These are the corrections most often needed (**Source-confirmed**, with the SDK access-key flag as **Reference**):

- **The browser session cookie does not authenticate a `REMOTECALL` call.** The platform's auth step reads only the `Authorization` header.
- **A valid token populates `AUTH`.** It holds `user`, `application`, and `lang`. Access tokens and legacy tokens are accepted. SDK and API tokens are not.
- **`AUTH.user` means any valid token.** It does not show which application the token was issued to. Compare `AUTH.application` where that matters.
- **An unauthenticated call can still execute.** A missing token and an invalid token both leave `AUTH` unset, and your script runs anyway. The platform does not answer 401 for you. Every protected handler must authorize explicitly. A restricted application does not change this: its permission check applies only when a user was identified, so an anonymous call still runs.
- **An access key is a separate branch.** If the service has an `accesskey`, the platform rejects a call without the key with 401. On success `AUTH` holds the key, not a user, so `$AUTH.user` is empty.
- **CORS is not authorization.** Every `REMOTECALL` response sends `Access-Control-Allow-Origin: *`. That header controls which pages may read a response. It does not restrict who can call the service.
- **A JSON body does not fill `REQUEST`.** Read it with `var_body` on `rest:resource` and decode it inside `try`.

### Bind globals inside `rest:resource`

Inside a handler, the request can create local variables, and locals hide globals (**Reference**). Query arguments become locals. For POST, form fields do instead (**Source-confirmed**). A global you read without binding it is therefore under the caller's control.

This matters most for authorization. Here `?AUTH[user]=1` passes the check with no token:

```xml
<!-- Wrong: a query argument can supply AUTH -->
<rest:resource route="/greeting/:name" method="GET">
  <is var="AUTH.user" type="empty">
    <set var="return">401</set>
    <return />
  </is>
</rest:resource>

<!-- Right: AUTH now refers to the platform value -->
<rest:resource route="/greeting/:name" method="GET">
  <use var="AUTH" />

  <is var="AUTH.user" type="empty">
    <set var="return">401</set>
    <return />
  </is>
</rest:resource>
```

:::warning
**The general rule:** inside `rest:resource`, any platform global (`AUTH`, `APPSETTINGS`, `REQUEST`, and so on) can be replaced by the request. Bind each one with `use` as the first child, before reading it. Do the same for your own globals, including resource controllers, macros, and shared arrays.
:::

- **A check before `rest:server` is not sufficient.** It reads the real `AUTH`, but `$AUTH.*` inside the handler can still be forged, for example `$AUTH.lang`.
- **Functions are not affected.** A function that the handler calls has its own context, so `$APPSETTINGS` inside a resource function reads the real global.
- **Route parameters win over query arguments** of the same name.
- **Any local you read before assigning it is also caller-controlled.** Assign your locals first.

This behavior was reproduced in a local run of the iXML engine with stand-in globals. Add a forged-argument test to every protected service. See [Checkpoint 4](https://www.zeyos.com/ixml/ixml-guide/development-environment/verification-checkpoints#checkpoint-4-runtime-verification).

:::warning
The `session` command reads the cookie on its own, independently of the auth step. If a browser sends the cookie to a `REMOTECALL` URL, `session` may return that user while `AUTH` stays unset. Do not build authorization on this. It has not been verified at runtime, it skips the restricted-application check, and a cookie-authorized service is exposed to cross-site request forgery.
:::

### Calling your own service from a weblet

The browser may send its cookie with the call, but the auth step ignores it, so a weblet frame that calls its own `REMOTECALL` service arrives unauthenticated. Do not paper over this by accepting anonymous calls or by shipping a long-lived token to the browser.

First ask whether the browser needs to call a service at all. The weblet already runs server-side in the user's session and can call the same resource directly, as the [minimal app](https://www.zeyos.com/ixml/ixml-guide/development-environment/minimal-app) does.

If the browser must call the service, that is a security design and it needs review. This guide does not publish a recipe. The design must define:

- **Principal binding:** which user the credential represents, set on the server from the session, never from a request parameter.
- **Route binding:** which routes, methods, and records it permits.
- **Expiry:** a short lifetime, checked on the server.
- **Replay policy:** whether reuse is allowed and how it is detected or limited.
- **Server-side key handling:** any signing key stays in instance settings and never reaches the page, the logs, or a response.
- **Verification:** tests for a missing, expired, tampered, wrong-user, and wrong-route credential, each run through a real HTTP request.

## `zeysdk run`

`zeysdk run` is console execution with application globals. It is not a simulated weblet and not an inbound HTTP request.

- It sends your **local** file to the instance and executes it there, so it can run code you have not pushed. Anything that file includes resolves to the **deployed** assets on that instance.
- It runs with the app's namespace and settings, so `$APPSETTINGS` and `.name` includes work.
- There is no session, no `AUTH`, no `REQUEST`, no `SELFURL`, and no `VIEW`. `session` returns NULL.
- Console requests use PUT, so `GET` and `POST` routes in `rest:server` do not match. **Logged:** on 6 February 2026, an inline REST example returned HTTP 404 and `null`.
- **Effects are real.** The console has no dry-run mode.

Use `run` to check a function or a query shape. Do not use it to conclude that a weblet, service route, or hook works. For compile-only checks, use `zeysdk compile`. See [Verification Checkpoints](https://www.zeyos.com/ixml/ixml-guide/development-environment/verification-checkpoints).

## TIMING services

The scheduler calls the service by `schedule` and `interval`. `schedule` is the start minute of the day, from `0` to `1439`, and `interval` is the repeat period in minutes. With an `interval` of `1`, `schedule` is ignored. The platform prevents concurrent runs of one service. All of this is **Source-confirmed**. Older `ref/sdk` pages show a `0` to `2359` time format, which the current SDK rejects. A skipped run is logged, not queued, so a job must be able to resume on its next run.

There is no user and no request. Log what the job did, because output is discarded.

## Entity hooks

Hook types are `AFTER_CREATION`, `BEFORE_MODIFICATION`, `AFTER_MODIFICATION`, `AFTER_CREATION_MODIFICATION`, `BEFORE_DELETION`, and `AFTER_DELETION`. Each needs an `entity` (**Reference**).

Inputs depend on the event (**Source-confirmed**):

| Event | Inputs besides `ENTITY` and `ID` |
| --- | --- |
| Creation | `NEW`, `NEW_EXTDATA`, `NEW_TAGS`, `MOD`, `MOD_EXTDATA`, `MOD_TAGS_ADD`. No `OLD` |
| Modification | All of the above, plus `OLD`, `OLD_EXTDATA`, `OLD_TAGS`, `MOD_TAGS_REMOVE` |
| Deletion | `OLD`, `OLD_EXTDATA`, `OLD_TAGS` only |

`AFTER_CREATION_MODIFICATION` runs for both creation and modification, so check `OLD` before using it.

A hook runs inside the transaction of the change that triggered it (**Source-confirmed**). This has three consequences:

- An uncaught error aborts the user's operation, including from an `AFTER_*` hook.
- Slow work in a hook slows every save of that entity. Move heavy work to a `TIMING` service.
- External side effects such as mail or HTTP calls are not rolled back with the transaction.

While a modification hook runs, a further change to the same record does not fire modification hooks again (**Source-confirmed**).

## Includes and JSON resources

`include` loads a resource by default. Use `type` to load a service or a weblet (**Reference**):

```xml
<include id=".Greeting" />
<include id=".api" type="service" />
<include id=".hello" type="weblet" />
```

The leading dot addresses an asset in the current app. It expands to the app identifier, so `.Greeting` in `example.minimal` means `example.minimal.Greeting`. This form is **Source-confirmed** and used throughout real apps, but `ref/ixmldoc.xml` does not describe it. It fails in a script that has no app namespace.

What a resource returns depends on its **MIME type**, not its extension (**Source-confirmed**):

| Resource MIME type | `include` returns |
| --- | --- |
| `application/ixml+xml`, `text/x-zymba` | The executed code's result |
| `application/json` | Decoded value |
| `application/xml`, `text/xml` | Parsed tree |
| `text/csv`, `text/tab-separated-values` | Parsed rows |
| Other text types | Text, with `$name` replaced from the include's parameters |

The SDK derives the MIME type from the extension when you add a file, which is why the two usually agree.

A JSON resource is already decoded, so `decode:json` on it is a mistake:

```xml
<!-- Wrong: the value is already an array -->
<include id=".defaults" var="raw" />
<decode:json var="defaults">$raw</decode:json>

<!-- Right -->
<include id=".defaults" var="defaults" />
<output>$defaults.title</output>
```

## Session locale

The `session` command returns an array, or NULL when there is no session (**Reference**). The reference does not list its keys.

The user's locale is in `data.lang` (**Source-confirmed**, and **Reported** from an app build in November 2026). A top-level `lang` key is not part of the contract, and `ref/dbref.json` lists no `lang` column on `users`.

```xml
<session var="session" />
<set var="lang">$session.data.lang</set>
```

In a `REMOTECALL` service, use `$AUTH.lang`. Always handle an empty value.

`session` returns NULL only when there is neither a session user nor a valid cookie. That makes the result context-dependent: a hook fired from a signed-in page sees that user, while the same hook fired by a `TIMING` service does not. Do not rely on `session` in hooks or in `REMOTECALL`.

## Common mistakes

- Treating a `zeysdk run` pass as proof that a weblet or service route works.
- Assuming a `REMOTECALL` handler is protected because the platform did not return 401.
- Reading `$AUTH` or `$APPSETTINGS` inside `rest:resource` without binding it with `use`.
- Treating `view` as an access control.
- Reading `$REQUEST.id` in a `.form` weblet, or `$ID` in a framed weblet.
- Sending a user-specific token to the browser to work around the missing session.
- Doing slow or external work inside a `BEFORE_*` hook.

## Related topics

- [Minimal App](https://www.zeyos.com/ixml/ixml-guide/development-environment/minimal-app)
- [Verification Checkpoints](https://www.zeyos.com/ixml/ixml-guide/development-environment/verification-checkpoints)
- [Troubleshooting](https://www.zeyos.com/ixml/ixml-guide/development-environment/troubleshooting)
- [App Structure](https://www.zeyos.com/ixml/ixml-guide/development-environment/app-structure)
- [Weblets](https://www.zeyos.com/ixml/ixml-guide/user-interface/weblets)

## Evidence

| Item | Record |
| --- | --- |
| Guide source | Revision `5271871` plus the working-tree changes of this update |
| References | `ref/ixmldoc.xml`, `ref/sdk/`, `ref/dbref.json`, `ref/openapi/sdk.json`, `LEARN.md` |
| SDK inspected | `zeysdk` 3.3.0, source and offline help |
| Checked | Static review against the references, reading the platform and SDK source, and running the `rest:resource` binding examples in a local copy of the iXML engine with stand-in globals |
| Not performed | Remote compilation, deployment, and execution on an instance. Apart from the dated log entry it cites and the local engine run, this page's claims have not been executed |

Open runtime checks are listed in `contributing/REVIEW_BACKLOG.md`.
