> For the complete documentation index, see [llms.txt](https://developer.vario-software.de/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://developer.vario-software.de/documentation/scripting/http-and-secrets.md).

# HTTP & Secrets

Call external HTTP APIs from scripts, restricted by the HTTP whitelist, with credentials kept in encrypted secrets.

VARIO Cloud scripts can call external HTTP APIs directly from the backend. This turns every workflow and batch script into an integration point: push data to a newsletter tool, post a message to a chat channel, ask an AI model for a product text, fetch leads from a form service or download images into the DMS, all without middleware.

Two safeguards keep this under control:

* **HTTP whitelist**: a script can only reach targets that an administrator has explicitly allowed.
* **Secrets**: passwords, API keys and tokens are stored encrypted and referenced by a placeholder, so they never appear in the script source.

Both are managed in VARIO Cloud under **System → Skripte**, in the sections **HTTP-Whitelist** and **Secrets**.

***

## HTTP Client

Scripts access HTTP through `ctx.services.httpClientService`. It creates a client, the client creates a request, and the request is sent with `execute()`.

| Method                                              | Description                                                                                        |
| --------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `createHttpClient()`                                | Client without authentication                                                                      |
| `createHttpClientWithBearerAuth(token)`             | Client that sends `Authorization: Bearer <token>`                                                  |
| `createHttpClientWithBasicAuth(username, password)` | Client that sends HTTP Basic authentication; the encoded header is logged, see [Secrets](#secrets) |

### Requests

A client offers `createGetRequest()`, `createPostRequest()` and `createPutRequest()`. `DELETE` and `PATCH` are not available.

| Request method              | Description                                          |
| --------------------------- | ---------------------------------------------------- |
| `uri(uri)`                  | Target URL (required)                                |
| `uriParameter(name, value)` | Adds a query parameter                               |
| `header(name, ...values)`   | Adds a header                                        |
| `contentType(type)`         | Sets the `Content-Type` header; not allowed on `GET` |
| `body(string)`              | Request body; not allowed on `GET`                   |
| `timeoutInSeconds(seconds)` | Request timeout, at most 300 seconds                 |
| `execute()`                 | Sends the request and returns the response           |

### Responses

| Response method                                                                         | Description                                                   |
| --------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
| `getHttpStatus()`                                                                       | HTTP status code                                              |
| `getBody()`                                                                             | Body as string (charset from the content type, default UTF-8) |
| `getBodyAsBytes()`                                                                      | Body as bytes                                                 |
| `getContentType()`                                                                      | Content type                                                  |
| `hasHeader(name)`, `getHeaderValue(name)`, `getHeaderValueAsList(name)`, `getHeaders()` | Response headers                                              |

```javascript
const { httpClientService, logger } = ctx.services;

const response = httpClientService.createHttpClient()
  .createGetRequest()
  .uri('https://api.example.com/v1/status')
  .uriParameter('lang', 'de')
  .header('Accept', 'application/json')
  .timeoutInSeconds(30)
  .execute();

const data = JSON.parse(response.getBody());
logger.info('Remote status: ' + data.status);
```

### Error Handling

A 4xx or 5xx response throws an exception with the message "HTTP-Request ist fehlgeschlagen: …", which names the status, method and URL. A 3xx response is returned normally; redirects are not followed. Catch the exception when a failing call must not abort the whole workflow or batch row:

```javascript
try
{
  client.createPostRequest()
    .uri('https://api.example.com/v1/contacts')
    .contentType('application/json')
    .body(JSON.stringify(payload))
    .execute();
}
catch (e)
{
  ctx.services.logger.warn('Sync failed, will retry on next save: ' + e);
}
```

### Limits

| Limit               | Value                                                            |
| ------------------- | ---------------------------------------------------------------- |
| Request body        | 10 MiB (counted in characters, before placeholders are resolved) |
| Response body       | 10 MiB; larger responses are cut off **without an error**        |
| Timeout per request | 300 seconds                                                      |
| Methods             | `GET`, `POST`, `PUT`                                             |

The same client is used by `shelfDocumentService.downloadIntoDMS()`, so downloads are subject to the same whitelist and limits. A file larger than 10 MiB is stored incomplete.

***

## HTTP Whitelist

Every outgoing request is checked against the whitelist. If no active entry matches, the request is rejected with the message "Request zu Domain … ist nicht erlaubt". **An empty whitelist blocks all requests.**

Each entry has a type and a value. Matching is case-insensitive and only active entries count.

| Type          | Matches                                                 | Example value                     | Allows                                                 | Does not allow                           |
| ------------- | ------------------------------------------------------- | --------------------------------- | ------------------------------------------------------ | ---------------------------------------- |
| `DOMAIN`      | Exact host name, any scheme and port                    | `api.example.com`                 | `https://api.example.com/any/path?x=1`, also `http://` | `https://eu.api.example.com/…`           |
| `URL`         | Exact URL, including the query string                   | `https://api.example.com/v1/data` | exactly this URL                                       | `https://api.example.com/v1/data?page=2` |
| `URL_PATTERN` | Whole URL against a pattern, `*` matches any characters | `https://api.example.com/v2/*`    | `https://api.example.com/v2/items`                     | `https://api.example.com/v1/items`       |

{% hint style="warning" %}
A `DOMAIN` entry covers exactly one host. Subdomains need their own entry or a `URL_PATTERN`. A value with a leading dot (`.example.com`) is accepted when saving but never matches a request.
{% endhint %}

Prefer the narrowest entry that works: a `DOMAIN` for one API host, a `URL_PATTERN` for a fixed path prefix. Keep the host part of a pattern literal.

{% hint style="danger" %}
`*` matches any characters, including `/` and `.`. A `*` in the host part therefore opens the whitelist to **any host**: `https://*.example.com/*` also matches `https://attacker.test/x.example.com/`. Use a separate `DOMAIN` entry per subdomain instead.
{% endhint %}

***

## Secrets

A secret is a named, encrypted set of key/value pairs, for example a secret `newsletter` with the keys `apiKey` and `listId`. Secrets are stored per tenant and encrypted at rest; the user interface never shows a stored value again.

Scripts have no service to read a secret. Instead, you write a **placeholder** into the request, and the HTTP client replaces it with the decrypted value just before the request leaves VARIO Cloud:

```
{{secret:<secretName>:<key>}}
```

A single string may contain several placeholders.

### Where Placeholders Are Resolved

| Location                                                   | Resolved                                              |
| ---------------------------------------------------------- | ----------------------------------------------------- |
| Header values (`header()`)                                 | Yes                                                   |
| Request body (`body()`)                                    | Yes                                                   |
| Bearer token (`createHttpClientWithBearerAuth()`)          | Yes, because it is sent as a header                   |
| `authValue` of `shelfDocumentService.downloadIntoDMS()`    | Yes                                                   |
| URL (`uri()`) and query parameters (`uriParameter()`)      | **No**                                                |
| Username and password of `createHttpClientWithBasicAuth()` | **No**, the credentials are encoded before resolution |

This means `createHttpClientWithBasicAuth()` cannot take placeholders, and you cannot put a secret into the URL. Literal credentials passed to `createHttpClientWithBasicAuth()` end up Base64-encoded in the log, which is as good as plain text. For Basic authentication, store the already Base64-encoded `username:password` as a secret value and send the header yourself: `header('Authorization', 'Basic {{secret:erp2shop:basic}}')`. If an API expects the key as a query parameter, check whether it accepts it as a header instead.

### Using a Secret

```javascript
const { httpClientService } = ctx.services;

// Bearer token
const client = httpClientService.createHttpClientWithBearerAuth('{{secret:openai:token}}');

// API key header
httpClientService.createHttpClient()
  .createPostRequest()
  .uri('https://api.brevo.com/v3/contacts')
  .header('api-key', '{{secret:brevo:apiKey}}')
  .contentType('application/json')
  .body(JSON.stringify({ email: 'max@example.com' }))
  .execute();

// Credentials inside a form body (OAuth client credentials)
httpClientService.createHttpClient()
  .createPostRequest()
  .uri('https://auth.example.com/oauth/token')
  .contentType('application/x-www-form-urlencoded')
  .body('grant_type=client_credentials'
    + '&client_id={{secret:partnerapi:clientId}}'
    + '&client_secret={{secret:partnerapi:clientSecret}}')
  .execute();
```

{% hint style="info" %}
Request method, URL, headers and body are written to the log **before** placeholders are resolved. With placeholders, the log shows `{{secret:…}}`. If you hard-code a key instead, it ends up in the log in plain text. Always use placeholders.

Exception: `shelfDocumentService.downloadIntoDMS()` with `BEARER_TOKEN` or `BASIC_AUTH` resolves the `authValue` first, so its log entry contains the real token or the Base64-encoded credentials. See [Downloading Files into the DMS](#downloading-files-into-the-dms).
{% endhint %}

Placeholders keep secrets out of the script source, but they do not make them unreadable for scripts. A script can send a placeholder to any whitelisted target, including one that echoes the request back. Keep the whitelist narrow; it is what protects your secrets.

### Errors

| Situation                        | Message                                       |
| -------------------------------- | --------------------------------------------- |
| Secret name does not exist       | "Secret mit dem Namen … wurde nicht gefunden" |
| Key does not exist in the secret | "Key '…' im Secret '…' wurde nicht gefunden"  |

Inside the HTTP client these errors are wrapped as "HTTP-Request ist fehlgeschlagen: …"; `downloadIntoDMS()` reports them unwrapped. The name of a missing secret is currently not filled into the message.

Placeholder values are inserted as they are, without URL or JSON escaping. If a secret contains characters like `"` or `&`, make sure the surrounding body format can carry them.

***

## Downloading Files into the DMS

`shelfDocumentService.downloadIntoDMS()` fetches a file over HTTP and stores it as a DMS document. It returns the created document, which you can then attach to a record.

| Method                                                       | Description                                                                                        |
| ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------- |
| `downloadIntoDMS(url, documentTypeKey)`                      | Download without authentication                                                                    |
| `downloadIntoDMS(url, authType, authValue, documentTypeKey)` | `authType` is `BEARER_TOKEN` or `BASIC_AUTH`; for `BASIC_AUTH`, `authValue` is `username:password` |

```javascript
const { shelfDocumentService, dtoFactory } = ctx.services;

const doc = shelfDocumentService.downloadIntoDMS(
  'https://media.supplier.example/images/4711.jpg',
  'BEARER_TOKEN',
  '{{secret:supplier:token}}',
  'IMAGE'
);

const attribution = dtoFactory.createShelfDocumentAttribution();
attribution.refType = 'PRODUCT_ARTICLE';
attribution.refId = articleId;
attribution.shelfResourceId = doc.id;
attribution.purpose = 'UNSPECIFIED';
shelfDocumentService.createAttribution(attribution);
```

{% hint style="warning" %}
With `BEARER_TOKEN` or `BASIC_AUTH`, the placeholder in `authValue` is resolved before the request is logged. The log entry therefore contains the real token or the Base64-encoded `username:password`. Restrict who can read the logs, or use a download URL that needs no authentication.
{% endhint %}

***

## Checklist for a New Integration

1. Add the API host to **System → Skripte → HTTP-Whitelist**.
2. Create a secret under **System → Skripte → Secrets** with the keys the API needs.
3. Reference the keys as `{{secret:<name>:<key>}}` in headers or body.
4. Wrap the call in `try`/`catch` and log failures.
5. Trigger the script from a workflow, a batch run or a scheduled script processing, see [Use Cases](/documentation/scripting/use-cases.md).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://developer.vario-software.de/documentation/scripting/http-and-secrets.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
