codeDock user manual
Run Python code without managing servers, connect it into workflows, and let people and systems know what happened. This manual explains every part of the portal, what it is for, and how to use it, with examples.
How codeDock fits together
Four building blocks, each with its own item in the menu.
Your apps: Python code with functions that answer a URL, run on a schedule, react to an event or a webhook, or run as a workflow step.
A canvas that puts step functions in order: what runs first, what runs after a success and what after an error.
Connections to databases, APIs, SharePoint, Google Drive and Hailer, set up once and used by name from code and workflows: also to move rows between them (ETL).
Send an e-mail or call webhooks (Slack, Teams, your own systems), from a URL, from a workflow block, or when a step fails.
The idea behind it: code decides what happens, the workflow decides in which order, and an action set tells the world. A workflow never contains code itself; each block runs a step function from one of your apps (a Code block), runs an action set (an Action set block), or reads or writes a data store (a Data store block).
Words used in this manual
- App
- One deployable unit of Python code with its own address, settings, keys and logs. Listed under Code.
- Function
- A Python function in an app with a decorator such as
@app.routethat says when it runs. - Deployment
- A numbered, unchangeable build of an app. One deployment is active and handles all calls.
- Invocation
- One run of one function, with its status, duration and log lines.
- Step function
- A function with
@app.step(), made to be a block in a workflow. - Workflow
- A trigger plus connected blocks. Each start of a workflow is a run.
- Parameter
- A named value, such as a city or a date, that you set on the workflow or a block instead of writing it in code.
- Run file
- A file a step stores in its run (temporary storage, 7 days), for later steps, for download and as an e-mail attachment.
- Action set
- A message (title, text, severity) plus a list of actions that send it. It fires from its URL or a workflow.
- Data store
- A named connection (database, API, SharePoint, Google Drive, Hailer) with its credentials, granted to chosen apps.
- Field key
- The fixed name of a field in a Hailer workflow, such as
order_no. codeDock names Hailer fields by key, so renaming a label in Hailer changes nothing. - Upsert
- Create or change: a row whose match value already exists changes that record, the others become new ones.
- GB-second
- What Pro bills computing by: the app's memory in GB times the call's duration in seconds (rounded up to 100 ms). See Plans and billing.
- Channel
- Where an action sends to: an e-mail recipient or a webhook URL. Set up once, used by any action set.
Finding your way
codeDock is a module of the wemovedata platform. After signing in you land on Home; inside codeDock the menu on the left is grouped by what you are doing.
Home and the module selector
- Home shows a tile for each wemovedata module you can use, with a few live figures (for codeDock: apps, workflows, data stores and your plan), and the apps, workflows and data stores you opened last on this device.
- The button at the top left of the menu shows where you are (Home or codeDock). Click it, or press the down arrow on it, to go to Home or another module. A module opens on the page you last had open there.
- Billing and Account belong to the platform: opened from codeDock, codeDock's menu stays; on Home, the menu has Home, Billing, Account and the manuals.
The codeDock menu
| Group | Item | What you do there |
|---|---|---|
| Build | Code | Create apps, edit and deploy code, manage settings, keys, Git and logs. |
| Workflows | Build workflows on the canvas, run them, see their runs and versions. Install and remove the examples here. | |
| Data stores | Connections to databases, APIs and file stores, which apps may use them, and connection tests. | |
| Operate | Monitor | All workflow runs of your account, with search. |
| Action sets | Action sets, the log of everything they sent, and channels (who and where). | |
| Account | Quotas | How many apps you have and how many invocations you used this month. |
| Billing | Your plan, this month's usage and the next invoice, billing details, spend cap and invoices. | |
| Account | Profile, two-factor authentication and personal access tokens. | |
| Help | User manual | This page, in a new tab. |
- The arrow at the top of the menu collapses it to icons; the portal remembers your choice on this device.
- On a phone, the menu button at the top opens the menu as a drawer; the module selector sits next to it.
- Your e-mail address and Sign out are at the bottom of the menu.
Buttons and icons
Lists have one New button at the top right to create something. Row actions are icons: hover one to see its name (screen readers announce the same name).
| Icon | Name | Where you find it |
|---|---|---|
| New / Insert step | Every list; a connection's panel | |
| Edit (Replace for a secret) | Action sets, channels, settings | |
| Delete, Move to trash, Delete forever | Workflows, action sets, channels, settings, connections | |
| Restore | The workflow trash | |
| Copy URL (turns into a tick when copied) | Action set URLs, app and webhook URLs | |
| Run | Workflow toolbar | |
| Run with payload… | Workflow toolbar | |
| Parameters | Workflow toolbar | |
| Edit code | A Code block's panel | |
| Send test, Resend link | Channels, action sets | |
| New URL | An action set's trigger URL | |
| Logs | Invocations, the steps of a run | |
| Refresh (Rotate for a key) | Deployments, runs, the action set log, keys | |
| Revoke | Keys | |
| Download | Files of a run | |
| Examples | Workflows |
Account and security
Who you are, how you prove it, and how scripts act on your behalf.
Signing up
- Sign up with your e-mail address and a password (or with GitHub when the platform offers it).
- Open the link in the verification e-mail. Until then you can look around but not create apps or workflows.
- Set up two-factor authentication under Account with an authenticator app such as 1Password, Google Authenticator or Authy. Keep the recovery codes somewhere safe; they are shown once and each works once.
Forgot your password? Use Forgot password on the sign-in page; the reset link is valid for a short time and works once.
When the portal asks for your authenticator code
Sensitive actions ask for a fresh code, at most every ten minutes: setting a secret, creating, rotating or revoking keys and tokens, turning a workflow webhook on, and connecting a Hailer phase to a workflow. This is called step-up. It means that a stolen browser session alone cannot read or create credentials.
Personal access tokens
Used for: scripts, CI pipelines and the codedock command line, which use the API without your password. Under Account → Personal access tokens press + New, choose a name, an expiry and only the scopes the script needs:
| Scope | Allows |
|---|---|
apps:read / apps:write | Read apps, deployments and settings (no secret values) / create and change apps |
deploy | Upload and activate deployments |
logs:read | Read invocations and logs |
settings:write / keys:write | Change settings and secrets / manage function keys |
workflows:read / workflows:write / workflows:run | See, change and start workflows |
notifications:read / notifications:write | See / change, test and delete action sets and channels |
The token is shown once; copy it into your password manager or CI secret store. A token cannot do step-up actions unless you gave it the matching scope and have two-factor authentication on.
Example. A GitHub Actions job that deploys an app needs only deploy. Store the token as a repository secret CODEDOCK_TOKEN and run codedock login --token "$CODEDOCK_TOKEN" followed by codedock deploy --app shop.
Signing in from the command line
codedock login --api https://<portal address> shows a short code and a link. Open the link, check that the code matches, choose the scopes and press Authorize; the command line then gets its own token, which you can revoke under Account like any other.
Code: your apps
An app is a folder of Python code with one or more functions, deployed as a whole. Code in the menu lists your apps.
Creating an app
Press + New and choose a name. The name becomes part of the app's address, https://<app>.<functions domain>, so it is unique on the platform and cannot be changed later: 4 to 32 lowercase letters, digits and hyphens, starting with a letter and ending with a letter or digit. You also pick the Python version (3.12, 3.13 or 3.14).
Tip. Use one app per purpose (shop-api, reports, weather-demo) rather than one big app. Each app has its own settings, keys and logs, so a mistake in one does not touch the others. Several apps can serve one workflow.
Overview
The first tab of an app. It shows the active deployment, every function with its trigger and URL (with a copy button), and the configuration:
- Python
- The version the app runs on. Changing it applies from the next deployment.
- Memory
- 128 to 1024 MB per instance (default 256). Raise it for pandas, large files or models.
- Timeout
- 1 to 300 seconds (default 30); a call that takes longer is stopped with 504. For workflow steps that call slow APIs, 60–120 s is a sensible value.
- Concurrency
- 1 to 16 calls one instance handles at the same time (default 1). Above 1, module-level objects must be thread-safe.
At the bottom, Delete app… removes the app with its deployments, settings, keys and logs. You type the app's name to confirm. Workflow blocks that use it then show it as not found.
Editor
Used for: changing code without installing anything. The file list is on the left; add files and folders with their path, such as mylib/pricing.py. Your changes are kept as a draft in this browser until you press Deploy, which builds a new deployment and activates it when the build succeeds. The editor completes the codeDock SDK as you type (see Completion).
Deployments
Every deploy, from the Editor, the command line or Git, is a numbered version that never changes. Each has a status (building, ready, failed) and a build log. Roll back (or Activate) on another ready version makes it active at once, without rebuilding. A failed build leaves the previous version running.
Logs and Invocations
- Logs is a live view of everything the app writes with
cd.logorprint, from all functions, with level and time. Use it while testing. - Invocations lists each call with its function, status, duration, memory and whether it was a cold start. Filter on a function or a status, and press Logs on a row to see only that call's lines.
- Every HTTP response carries
x-codedock-invocation-id; search for it under Invocations to find one call. Logs are kept for 7 days.
Settings and secrets
Used for: configuration that differs per app or must not be in code, such as API tokens, database URLs and feature switches. Your code reads them as environment variables.
import os
API_URL = os.environ.get("API_URL", "https://api.example.com") # a plain setting
TOKEN = os.environ["CRM_TOKEN"] # a secret: KeyError if missing
- Press + New, give a name (letters, digits and underscores; not starting with
KWF_) and a value of up to 32 KB. - Tick Secret for anything sensitive. It is encrypted, never shown again, and setting it asks for your authenticator code. Replace sets a new value.
- Settings are read when an instance starts. After a change, deploy again to use the new value everywhere.
Keys
Used for: protecting HTTP functions that are not public. Press + New and choose the scope:
- Master key
- Works for every function of the app (
kwf_mk_…). - Function key
- Works for one function only (
kwf_fk_…).
The key is shown once. Callers send it in the x-functions-key header. Rotate gives a new value and the old one stops working; Revoke removes the key. Give each caller its own key so you can revoke one without breaking the others.
curl -H "x-functions-key: $KEY" https://shop.<functions domain>/api/orders/42
Git
Used for: deploying on every push, so the repository is the source of truth. Enter a public GitHub repository (owner/name), the branch and, optionally, the folder that holds function_app.py. The portal then shows a webhook URL and a secret (once): add both in GitHub under Settings → Webhooks with content type application/json. Each push to the branch builds and activates a new deployment, listed under Deployments. Reconnecting creates a new secret.
Writing functions
Functions live in function_app.py. A decorator says when each one runs.
import codedock as cd
app = cd.FunctionApp()
@app.route(route="hello", methods=["GET"], auth_level=cd.AuthLevel.ANONYMOUS)
def hello(req: cd.HttpRequest) -> str:
name = req.params.get("name", "world")
cd.log.info("greeting %s", name)
return f"Hello {name}"
| Decorator | Runs when | Use it for |
|---|---|---|
@app.route(route=…) | Someone calls https://<app>.<domain>/api/<route> | APIs, small web pages, forms |
@app.schedule("0 */15 * * * *") | On a schedule (six fields, seconds first, UTC) | Clean-ups, syncs, reports |
@app.event("order.created") | Any app of your account publishes that topic with cd.publish(…) | Handing slow work off, letting apps react to each other |
@app.webhook(name=…, secret_setting=…) | Another system posts a signed request to /webhooks/<name> | GitHub, Stripe, your own systems |
@app.step() | A workflow runs it as a Code block | One step of a process |
Project layout
shop/
├── function_app.py # required: defines app = cd.FunctionApp()
├── requirements.txt # optional: packages from PyPI (wheels only)
└── mylib/pricing.py # your own modules: from mylib import pricing
At build time the platform imports your file and reads the decorators; an error at import fails the build and shows the traceback. Keep import-time code light and free of network calls. Only /tmp is writable (64 MB, cleared when the instance stops).
HTTP functions
@app.route(route="orders/{order_id}", methods=["GET", "DELETE"])
def order(req: cd.HttpRequest):
oid = req.route_params["order_id"] # from the path
verbose = req.params.get("verbose") # from the query string
if req.method == "DELETE":
return cd.HttpResponse(status_code=204)
return {"id": oid, "verbose": verbose} # a dict becomes JSON
req.get_json()parses a JSON body,req.get_body()gives the bytes,req.headers.get("content-type")reads a header (case-insensitive).- Return a
str(text), adictorlist(JSON),bytes,None(204), orcd.HttpResponse(body, status_code=…, headers=…, mimetype=…)for full control. - Requests and responses can be at most 6 MB.
Who may call an HTTP function
ANONYMOUS- Anyone. For public pages, and endpoints that check their own credentials.
FUNCTION- Callers with a key for this function or the master key (the default).
ADMIN- Only callers with the master key.
Change the default for the whole app with cd.FunctionApp(http_auth_level=cd.AuthLevel.ANONYMOUS) and override it per route with auth_level=.
Keys belong on servers. Anything shipped to a browser or a phone app is public: use ANONYMOUS there and check a session or token in your function.
Schedules
@app.schedule("0 30 6 * * 1-5") # 06:30 UTC, Monday to Friday
def morning_sync(timer: cd.TimerRequest) -> None:
if timer.past_due:
cd.log.warning("late; was due at %s", timer.scheduled_at)
...
Six fields, all in UTC: second minute hour day month weekday (0 = Sunday). Fields take *, numbers, ranges 1-5, lists 8,17 and steps */15.
| Schedule | Runs |
|---|---|
0 * * * * * | every minute |
0 0 * * * * | every hour |
0 0 6 * * * | daily at 06:00 UTC (07:00 or 08:00 in Amsterdam) |
0 0 8,17 * * 1-5 | 08:00 and 17:00 UTC on weekdays |
0 0 0 1 * * | midnight UTC on the first of each month |
Events
Used for: answering a request quickly and doing the slow part in the background, or letting one app react to another.
# app "orders"
@app.route(route="orders", methods=["POST"])
def place_order(req: cd.HttpRequest):
order = req.get_json()
cd.publish("order.created", {"id": order["id"], "total": order["total"]})
return cd.HttpResponse(status_code=202)
# app "mailer", same account
@app.event("order.created")
def send_receipt(event: cd.Event) -> None:
order = event.get_json()
cd.log.info("receipt for %s", order["id"])
An event reaches every subscribed function in every app of your account, and can also start a workflow. Payloads are at most 256 KB. Topics are lowercase letters, digits, ., _ and -; subscribe to the exact topic.
Webhooks
Used for: receiving signed calls from GitHub, Stripe or your own systems. The platform checks the signature with a secret from your settings, answers 202 at once and runs your function in the background; repeated deliveries run only once.
@app.webhook(name="github", secret_setting="GITHUB_WEBHOOK_SECRET")
def on_push(event: cd.Event) -> None:
kind = event.headers.get("x-github-event")
repo = event.get_json()["repository"]["full_name"]
cd.log.info("%s on %s", kind, repo)
Store the secret under Settings as a secret named GITHUB_WEBHOOK_SECRET, and put the same value in GitHub. GitHub's X-Hub-Signature-256 and codeDock's X-Codedock-Timestamp + X-Codedock-Signature: sha256=… (an HMAC of <timestamp>.<body>) are both accepted.
Background functions (schedules, events, webhooks) that fail are tried again after 1 s, 10 s, 1 min, 5 min and 30 min. Make them safe to run twice, for example by using event.id as a key.
Workflow steps
@app.step()
def enrich(step: cd.StepInput):
customer = lookup(step.input["customer_id"])
if customer is None:
raise cd.StepError("unknown customer") # status error: the red path runs
if not customer.get("email"):
return cd.StepResult(customer, status="warning", message="no e-mail address")
return customer # status success: the output for the next block
See Workflows for how steps are connected and Parameters and run files for everything a step receives.
Logging, context and the network
cd.log.info("…%s", value),.warning,.errorand.exception(insideexcept, with the traceback).printworks too. Never log secrets.cd.context.invocation_idandcd.context.function_nametell you which call this is; put the id in error messages.- Your code can call any public HTTPS address. Private networks, the platform's own services, mail ports and IPv6 are blocked. Always set a timeout on outgoing calls, shorter than the app's timeout.
- Module-level objects (HTTP clients, loaded data) are reused between calls of one instance; treat them as a cache, never as storage.
Completion in the editor
The code editor (an app's Editor and the code popup on the workflow canvas) knows the codeDock SDK:
- Type
cd.,step.,req.,event.ortimer.for what is available, with a short description;@app.lists the decorators with their main arguments filled in. - Hover a name to read what it is; inside a call, the parameters are shown as you type.
- At the start of a line, type
route,schedule,event,webhook,step,step_fileorstep_errorand press Enter for a ready function; Tab jumps to the next value to fill in. - Ctrl+Space opens the list at any time. Names from your own file and Python's keywords are in it too; packages from
requirements.txtare not.
Workflows
A workflow has one trigger and up to 50 blocks, connected by arrows. Used for: processes with several steps, such as "fetch, check, convert, send", where you want to see each step, retry the right one and be told when something breaks.
The workflow list
- New creates an empty workflow and opens it. Examples installs a ready-made one (see the example).
- Search on name or description. moves a workflow to the Trash tab, where restores it and deletes it forever. A workflow in the trash does not run.
The toolbar
- Name
- The workflow's name, at most 64 characters, unique in your account.
- Save
- Saves a new version. It stays grey while every Code block does not yet have an app and a step function, and every Action set block an action set.
- Parameters
- The workflow's parameters, for every block (see Parameters).
- Run
- Starts a run of the saved version straight away. The canvas colours the blocks live.
- Run with payload
- Starts a run with a JSON payload you type, available to every step as
step.payload(and as the first step'sstep.input). - Move to trash
- Stops the workflow from running until you restore it.
- Description
- The large text field under the toolbar. Write what the workflow does, when it runs and who gets its results; colleagues read it in the list.
Building on the canvas
- Press + under the trigger to add the first block. Under a block, the green + adds a block that runs when it succeeds, the red one a block that runs when it fails.
- Click a block to edit it in the panel on the right. Choose what it does under Block:
- Code: under Code pick the app, then the Step function. Edit code, right under the app, opens its code in a popup where you can change and deploy it. + New app… and + New step function… write new code without leaving the canvas.
- Action set: pick an action set and the run files to attach. See Action sets.
- Data store: query a database, call an API, or upload or download files, without code. See Data stores.
- Give the block a Label that says what it does, such as "Fetch forecast". The label shows on the canvas and in runs.
- Drag from a handle to another block to connect them; right-click a connection to insert a block in between. Select a block or connection and press Delete (or the button in its panel) to remove it.
- Press Save. Every save is a new version; runs always record the version they ran.
The block panel in detail
- Label
- The name on the canvas.
- Block
- Code or Action set.
- Code / Step function
- The app and the
@app.step()function this block runs. Only functions in the app's active deployment are listed. - Parameters
- Values for this block only. They win over the workflow's parameters with the same name.
- Action sets: when the step fails / succeeds
- An action set to fire when this block ends in error or failure, or succeeds. For example "Workflow problems" on every block that can fail.
- On
- Untick to skip the block (and what depends only on it) without deleting it.
When blocks run
- A block runs when the blocks before it are done and the arrow's condition matches: always, succeeded or ended in error or failure. Click a connection to change its condition or give it a label.
- Blocks whose inputs are ready run in parallel.
- A step that raises
cd.StepErrorends in error; any other exception, a timeout or running out of memory is a failure. Both follow the red arrows. With no red arrow, the run ends there. - A step can also end in warning or informational with
cd.StepResult(…, status="warning"); those count as success.
The trigger: how a run starts
Click the trigger block at the top to set how runs start. Several can be on at once.
| How | Set it up | Example |
|---|---|---|
| By hand | Always on: Run in the toolbar, or codedock workflow run. | Testing; a report someone asks for |
| Schedule | A six-field UTC schedule. | 0 0 6 * * *: every morning at 06:00 UTC |
| Event | A topic; every cd.publish("topic", …) in your apps starts a run with the event as payload. | order.created starts "Process order" |
| Webhook | Turn it on to get https://wf.<domain>/<workflow id> with a signing secret and a key (shown once). Callers sign the body like app webhooks or send x-workflow-key. Add ?wait=30 to wait up to 30 s for the result. | A form tool or another system starts a run |
| Hailer | Connect a phase of a Hailer workflow: a run per activity that moves into the phase or changes in it, with its fields by key as payload. See Start from Hailer. | An order moved to Approved is sent to the ERP |
curl -X POST "https://wf.<domain>/<workflow id>?wait=30" \
-H "x-workflow-key: $WORKFLOW_KEY" -H "Content-Type: application/json" \
-d '{"customer_id": 7}'
The trigger also has When the run fails / succeeds: an action set for the run as a whole, for example a single "done" e-mail at the end.
Tabs: Workflow, Runs, JSON, Versions
- Workflow
- The canvas and the block panel.
- Runs
- Recent runs. Open one for each block's status, duration, input and output, its logs () and the run's files ().
- JSON
- The whole workflow as JSON, to copy, review or keep in Git.
codedock workflow apply FILEcreates or updates a workflow from such a file. - Versions
- Every saved version. Activate makes an earlier one current again.
Parameters and run files
What a step receives
step.input- The output of the block before it. With several blocks leading in, a dict keyed by block id. For the first block, the run's payload.
step.payload- What the run started with.
step.params- The workflow's parameters, with this block's own parameters on top. All values are strings.
step.files- Files that earlier blocks of this run stored.
step.run_id,step.step,step.attempt- Which run and block this is, and the attempt. Together a good key to make a step safe to run twice.
Parameters
Used for: values you may want to change without touching code, so one step function serves many workflows. A city, coordinates, a date range, a file name, an e-mail subject.
- Parameters in the toolbar: the workflow's parameters, for every block. Up to 50.
- Parameters in a block's panel: values for that block only, up to 20. A name that also exists on the workflow overrides it; the panel shows the workflow's value as a hint.
OUTPUT_MESSAGE, when set on the workflow, is the message shown for each run in the run list.- Parameters are also placeholders in action sets:
{{place}}in a message becomes the value ofplace.
Example. Workflow parameters latitude = 52.3676 and longitude = 4.9041; a block parameter end_date = today+6. The step reads them all from one place:
@app.step()
def fetch_forecast(step: cd.StepInput):
lat = step.params.get("latitude", "52.3676")
lon = step.params.get("longitude", "4.9041")
end = step.params.get("end_date", "today+6")
...
Change latitude and longitude on the workflow and every block that uses them follows.
Parameters are visible to everyone who can open the workflow. Keep passwords and API tokens in the app's secret settings.
Run files: temporary storage
Used for: passing a file from one step to the next, offering it for download, and attaching it to an e-mail. For example an API response saved as CSV. Data store blocks store run files too (a query or Hailer list as CSV, a download), and can run once for each row of a CSV run file: see ETL.
import csv, io
@app.step()
def to_csv(step: cd.StepInput):
rows = step.input["rows"]
buf = io.StringIO()
writer = csv.writer(buf)
writer.writerow(["time", "temperature"])
writer.writerows(rows)
return cd.StepResult({"rows": len(rows)}, files=[cd.File("report.csv", buf.getvalue(), "text/csv")])
@app.step()
def count_lines(step: cd.StepInput):
f = next(x for x in step.files if x.name == "report.csv")
return {"lines": len(f.text().splitlines())}
- Later steps read a file with
.read()(bytes) or.text();.name,.size,.content_typeand.stepdescribe it. - The run view lists the files with a download link.
- Files are kept for 7 days. At most 4 MiB per step, and 20 files and 16 MiB per run. A file with the same name as an earlier one replaces it.
- Later steps get the content while the run's files are 2 MiB or less together; above that they get the list only, and
read()returnsNone.
Action sets
Tell people and systems that something happened. The page has three tabs: Action sets, Log and Channels.
Used for: e-mailing a report from a workflow, posting in a Slack or Teams channel when a job fails, letting a monitoring tool page you by calling a URL, or calling your own system when a run is done.
Channels: who and where
Add channels once under Action sets → Channels and reuse them in every action set. Give each a name and an icon you recognise.
- E-mail recipients (+ New in that list). Your own verified address works at once. Any other address first gets a confirmation link (valid for 7 days) and receives nothing until its owner confirms; until then it shows waiting for confirmation. Resend link sends a new one.
- Webhooks (+ New in that list). Paste a URL. Slack, Microsoft Teams and Discord URLs are recognised and get their own message format; anything else gets standard JSON. You can choose GET, POST or PUT, add headers (their values are encrypted and never shown again), and turn on signing: each call then carries
X-Codedock-TimestampandX-Codedock-Signature: sha256=…, an HMAC of<timestamp>.<body>with the secret shown once.
Send test on a channel sends a test message straight away. Each channel lists the action sets that use it; deleting a channel removes it from them. Webhooks can only call public internet addresses.
Example: Slack. In Slack, create an incoming webhook for #ops and copy its URL. In codeDock, add a webhook channel, paste the URL (the Slack icon and format are chosen for you), name it "#ops" and press Send test. A message appears in #ops.
Creating an action set
Under the Action sets tab press New. Fill in:
- Name and description
- What it is for, such as "Disk full" or "Weather report".
- Severity
- info, warning or critical. Shown in the e-mail subject and the webhook body; a caller can override it with a
severityparameter. - Cooldown
- Off by default. With a cooldown, fires within that time after the last one are logged as suppressed and send nothing. Use it for monitors that call every minute.
- Title and message
- The text that is sent, with placeholders (below). The editor shows a live preview.
- Actions
- An ordered list. Each action is Send an e-mail to a recipient or Call webhook for a webhook channel. Reorder with ↑ ↓, remove with .
- On/off
- The switch in the list. An action set that is off answers its URL with 404; an Action set block that uses it ends in error and sends nothing. Use it to silence something for a while without deleting it.
Placeholders
{{name}}for any value the caller passes, such as?host=db1→{{host}}.- Always available:
{{action_set}},{{severity}}and{{time}}. - From a workflow: the previous step's top-level output fields, the parameters,
{{workflow}}and{{run_url}}. For a failing or succeeding step also{{step}},{{status}},{{error}}and{{message}}. - An unknown placeholder becomes empty.
Title: {{host}}: disk {{pct}}% full
Message: {{action_set}} at {{time}}. Clean up /var/log on {{host}}.
Send test in the editor fires the saved action set with values you type, even when it is off. deletes it with its log; workflow blocks that used it then show (deleted action set).
Three ways to fire an action set
1. Its URL
Every action set has a secret URL. Copy it with . Query parameters or a JSON body become placeholders.
curl 'https://notify.<domain>/<id>?host=db1&pct=97'
curl -X POST 'https://notify.<domain>/<id>' \
-H 'Content-Type: application/json' -d '{"host": "db1", "pct": "97", "severity": "critical"}'
Anyone with the URL can fire it, so keep it private. New URL replaces it and the old one stops working at once.
2. An Action set block in a workflow
Set a block to Action set, choose the action set and, under Attach run files, the files to attach, such as *.csv or report.pdf, *.csv. Matching files go with the e-mails, up to 10 MiB per e-mail; webhooks get their names and sizes. The block runs no code and counts no invocation.
3. When a step or run fails or succeeds
Every Code block has When the step fails and When the step succeeds; the trigger has the same choices for the whole run. Choose an action set, for example one that e-mails you when anything breaks:
Title: {{workflow}}: {{step}} ended in {{status}}
Message: {{error}}{{message}}
Open the run: {{run_url}}
Limits
Each URL takes at most 30 calls a minute, and an account sends at most 200 action set e-mails per 24 hours. An action set has at most 10 actions; an account at most 50 action sets and 50 channels.
The log
Every fire is in Action sets → Log, newest first, with its parameters, where it came from (URL, test, workflow) and the result of each action. Filter on an action set or a status, search, and refresh.
| Status | Meaning |
|---|---|
| pending | Being sent. |
| sent | Every action succeeded. |
| partial | Some actions succeeded; open it to see which did not and why. |
| failed | No action succeeded, for example an unconfirmed recipient. |
| suppressed | Within the cooldown: logged, nothing sent. |
| no actions | The action set has no actions. |
When a mail server or webhook is temporarily unavailable, codeDock tries again after 30 seconds, 2, 5, 10 and 15 minutes; the action shows retrying meanwhile.
Data stores
A data store keeps the address and credentials of a database, an API or a file store. Set it up once under Data stores, choose which apps may use it, and use it by name: in code as cd.db("sales"), in a workflow as a Data store block.
Used for: reading orders from your database, writing results back, calling a CRM or ERP API whose token expires every hour, saving reports to SharePoint, picking up files from Google Drive, or reading and filling Hailer workflows, without passwords or token code in your apps. Blocks also move rows from one store into another without code (ETL).
Kinds
| Kind | You fill in | Signs in with |
|---|---|---|
| PostgreSQL | host, port (5432), database, user, encryption | password |
| MySQL / MariaDB | host, port (3306), database, user, encryption | password |
| SQL Server / Azure SQL | host, port (1433), database (optional), user, encryption | SQL login and password |
| Oracle | host, port (1521), service name, user, encryption | password (no Oracle client needed) |
| API connection | base address and how it signs in | API key header, Basic, OAuth2 client credentials, or OAuth2 sign-in with a refresh token |
| SharePoint | site address, directory (tenant) ID, application (client) ID | client secret of your Entra app registration (app-only) |
| Google Drive | folder ID (optional) | a service account's JSON key |
| Hailer | the user's e-mail address, then the workspace from a list | the user's password (codeDock keeps the session and renews it) |
Creating a data store
- Go to Data stores and press New, then pick the kind.
- Give it a Name (lowercase letters, digits,
-and_, such assales). Your code and workflows use this name, so choose one that says what it is, not where it runs. - Fill in the connection and the credentials. Passwords, keys and secrets are encrypted, never shown again, and saving them asks for a code from your authenticator app. A stored secret shows stored; leave its field empty to keep it.
- Under Apps that may use it, tick the apps. Only those apps get the store; others get an error that names it. Running instances of those apps restart within a minute to pick up the change.
- Press Create data store, then Test connection. The result and its time stay on the store; the list shows connected, failing, not tested or incomplete (a credential is missing).
Encryption
- Encrypted, certificate checked (default): the connection uses TLS and the server must prove its name.
- Encrypted, certificate not checked: for servers with a self-signed certificate.
- Not encrypted: only for servers that cannot do TLS. Oracle listeners often only speak plain TCP on 1521; TLS (TCPS) is usually on 2484.
From Python code, SQL Server connections are always encrypted when you choose either encrypted option, but the driver does not check the certificate.
API connections and tokens
- API key in a header
- The key goes in the header you name, such as
X-API-Key. ForAuthorization, include the scheme in the key:Bearer abc…. - Basic
- A user name and password.
- OAuth2 client credentials
- For server-to-server APIs: the token URL, client ID, client secret and scopes from the provider. codeDock gets the access token and renews it before it expires.
- OAuth2 sign-in (refresh token)
- For APIs that act for a user. Fill in the authorization URL, token URL, client ID, scopes (often including
offline_access) and, if the provider gave one, the client secret. Register the Redirect URI shown on the store with the provider, save, and press Sign in. You sign in at the provider and come back to the store. codeDock keeps the refresh token and stores a new one whenever the provider rotates it.
Tokens are renewed by codeDock, a few minutes before they expire, never by your code. That way a refresh token is never used twice at the same time. If renewing fails, the store shows token failing with the provider's answer; fix the credentials or sign in again.
SharePoint
- In the Microsoft Entra admin centre, register an application and add a client secret. Note the Directory (tenant) ID, the Application (client) ID and the secret.
- Under API permissions, add the Microsoft Graph application permission
Sites.Selected(and grant the app access to the one site) orSites.ReadWrite.All, and grant admin consent. - In codeDock, enter the site address (such as
https://contoso.sharepoint.com/sites/finance), the two IDs and the secret, and test.
Paths are folders and files in the site's Documents library, such as Reports/2026/january.xlsx; a leading Shared Documents/ is accepted too.
Google Drive
- In the Google Cloud console, enable the Google Drive API, create a service account and add a JSON key.
- In codeDock, paste the whole key file and, optionally, the ID of a folder (the last part of its address in Drive). Save.
- The store shows the service account's e-mail address. In Drive, share the folder (or shared drive) with it as Editor. Then test.
Hailer
- In Hailer, invite a user of its own for codeDock (such as
integration@yourcompany.com) without two-step sign-in, and give it access to the workflows codeDock should read or change. codeDock sees exactly what this user sees. To start workflows from a Hailer phase, the user must also be allowed to change those workflows' settings (codeDock sets the phase's webhook). - In codeDock, create a Hailer data store with that e-mail address and password (choose Hailer staging only for Hailer's test environment). Save.
- Press Load workspaces, choose the workspace, save and test.
- Workflows and field keys on the store lists every workflow with its phases and fields. Code and blocks name fields by their key. A field without a key shows no key; give it one in Hailer's workflow settings (a key survives renaming the label), or use its id.
In your code
Import the SDK as usual; a store an app may not use raises cd.DataStoreError with what to do.
# Databases: the driver's own connection, or the helpers
rows = cd.db("sales").query("select id, total from orders where day = %s", [day]) # list of dicts
n = cd.db("sales").execute("update orders set done = true where id = %s", [order_id]) # rows changed
cd.db("sales").execute_many("insert into prices (day, eur) values (%s, %s)", [(d, p) for d, p in prices])
with cd.db("sales").connect() as conn: # psycopg, PyMySQL, pymssql or python-oracledb
with conn.cursor() as cur:
cur.execute("select count(*) from orders")
# APIs: relative to the base address, credentials added
r = cd.api("crm").get("/contacts", params={"page": 1})
contacts = r.raise_for_status().json()
cd.api("crm").post("/contacts", json={"name": "Ada"})
token = cd.api("crm").token() # OAuth connections: the current access token
# SharePoint
for f in cd.sharepoint("finance").list("Reports"):
print(f["name"], f["size"], f["folder"])
data = cd.sharepoint("finance").download("Reports/january.xlsx")
cd.sharepoint("finance").upload("Reports/weather.csv", csv_text, "text/csv")
# Google Drive (by name in the store's folder, or by file id)
files = cd.gdrive("exports").list()
data = cd.gdrive("exports").download("input.csv")
cd.gdrive("exports").upload("weather.csv", csv_text, "text/csv") # replaces a file with the same name
# Hailer: workflows and phases by name (or id), fields by key
h = cd.hailer("crm")
for o in h.list("Orders", phase="New"): # dicts: _id, name, phase, created, updated, the fields by key
print(o["_id"], o["customer"], o["amount"])
oid = h.create("Orders", {"customer": "Ada", "amount": 12.5, "day": "2026-10-31"}, name="Ada's order")
h.update("Orders", oid, {"amount": 15}, phase="Done")
h.create_many("Orders", [{"_name": r["id"], "customer": r["name"], "amount": r["total"]} for r in rows])
h.upsert("Orders", [{"order_no": r["id"], "amount": r["total"]} for r in rows], key="order_no") # create or change
h.workflows() # the workspace's workflows, phases and fields
h.call("v3.activity.count", h.workflow("Orders")["id"]) # any other Hailer API operation
- Placeholders follow the driver:
%sfor PostgreSQL, MySQL and SQL Server,:1or:namefor Oracle. Never paste values into SQL yourself. query,executeandexecute_manyopen a connection, commit and close it. For many statements in one transaction, useconnect().- The database drivers come with codeDock; you do not add them to
requirements.txt. A version you do add there is used instead. cd.apionly sends credentials to the store's own host; a full address on another host raises an error. A 401 is retried once with a renewed token.- Tokens are fetched while a function runs, so call these inside your function, not at import time.
The Data store block
In a workflow, set a block's Block to Data store, choose the store and what it does. It runs no code, so it costs no invocation. Workflow blocks may use every store of your account; the app list does not apply to them.
| Store | Does | Output |
|---|---|---|
| Database | Run a query: your SQL; optionally also stores all rows as a CSV run file | rows (first 500), count, columns, truncated, file |
| Database | Run a statement: insert, update, delete, a procedure call | rows_affected |
| API | Call the API: method, path, JSON body; optionally stores the answer as a run file | status and body (the parsed JSON) |
| SharePoint, Drive | Upload run files matching patterns such as *.csv to a folder | uploaded, folder |
| SharePoint, Drive | Download a file into the run | file, size |
| Hailer | List activities of a workflow, in one phase or all; optionally stores them as a CSV run file | rows (first 500, fields by key), count, columns, file |
| Hailer | Create an activity: name, start phase and field values by key | id of the new activity |
| Hailer | Change an activity (usually {{id}}): field values, name, or move it to a phase | id, changed |
| Hailer | Upsert the rows of a file: each row of a CSV run file creates or changes an activity, matched on a field (ETL) | created, updated, failed, failed_file |
{{name}} takes a value from the output of the block before (its top-level fields), the block's and the workflow's parameters, and the run ({{run_id}}, {{workflow}}). In SQL it becomes a bound parameter, never text, so where customer = {{customer}} is safe even when the value contains quotes; do not put quotes around it. In a path it is URL-encoded, in a JSON body escaped for a string ("name": "{{name}}").
A failing query, a refused call (status 400 or more) or a missing file takes the block's error path with the message of the database or API. Like an action set block, a data store block runs once: when the run is interrupted while it runs, it ends in failure rather than running a statement twice.
Example: last week's orders to SharePoint
- Data stores
shop(PostgreSQL) andfinance(SharePoint). - A workflow: trigger with schedule
0 0 7 * * 1→ Data store block Run a query onshop:select day, count(*) as orders, sum(total) as total from orders where day >= current_date - 7 group by day order by day, stored asorders.csv→ Data store block Upload run files tofinance, files*.csv, folderReports/Weekly→ Action set block that e-mails the team.
Example: the weather into Hailer
- In Hailer, a workflow Weather with fields keyed
city(text),temp_c(number) andday(date), and a Hailer data storehailer. - In the weather example's workflow, add a Data store block after the step that returns the day's values (say
{"city": "Amsterdam", "temp": 11.2, "day": "2026-10-31"}): Create an activity in Weather, name{{city}} {{day}}, fieldscity={{city}},temp_c={{temp}},day={{day}}. - A block after it can Change an activity
{{id}}, for example to move it to Done.
ETL: rows from and to Hailer
An ETL workflow reads rows into a CSV run file, then loads that file somewhere else. Every source can feed every target: a database query, a Hailer list or a downloaded file on one side; Hailer, a database, an API or SharePoint and Drive on the other.
- Read: Run a query or List activities with Also store the rows as CSV (say
orders.csv), or Download a file / Call the API that returns CSV. Files may have,or;between values, up to 100 000 rows; a run keeps at most 16 MiB of files. - Load, row by row: Run a statement and Call the API have For each row of: name the file, and the block runs once per row, with
{{column}}taking that row's value (the column names are the file's header). Upsert the rows of a file does the same into Hailer. - Upsert into Hailer: choose the workflow, Match on a field that identifies a row (an order number, an e-mail address), and give each field a value, usually
{{column}}; Fill in columns named like the keys does that for columns named like the field keys. A row whose match value an activity already has changes that activity; the others become new activities in the phase you choose. Rows repeating a match value count once (the last one). - Failed rows do not stop the rest. The block ends with a warning (an error when every row failed), lists the first 20 reasons, and stores the failed rows with an
errorcolumn asorders_failed.csv; mail it with an Action set block, or fix it and load it again. - Only what changed: tick Only activities changed since the last successful run on a list, or use
{{last_success}}in SQL:where updated_at > {{last_success}}::timestamptz. It is when the last run that ended in success started; a run with a warning does not move it, so those changes are read again. - Transforms beyond templates (sums, lookups, splitting a column) are a Code block that reads the file from
step.filesand returns a new one (steps get file contents while the run's files are 2 MiB or less together; for more, query or list less at a time).
Start from Hailer
On the trigger, Hailer → Connect a phase: choose the store, workflow and phase, and whether a run starts when an activity moves into the phase, when one changes in it, or both. codeDock sets the phase's webhook in Hailer to a secret address of this workflow (the Hailer user needs the right to change the workflow). Each activity Hailer sends starts one run; its payload is the activity with fields by key, so blocks use {{id}}, {{name}}, {{phase}} and {{your_key}}. A phase calls only one address: if it already calls another, codeDock asks before replacing it. Disconnect turns the webhook off in Hailer again.
Careful with loops: a run that changes the same activity in the phase that starts it starts itself again. Move the activity to another phase, or start only on moves into the phase.
ETL examples
- Orders from PostgreSQL into Hailer, every hour
- Schedule
0 0 * * * *→ Run a query onshop:select order_no, customer, total from orders where updated_at > {{last_success}}::timestamptz, stored asorders.csv→ Upsert the rows of a file into Orders, rows fromorders.csv, match onorder_no, fieldsorder_no={{order_no}},customer={{customer}},amount={{total}}. - Hailer into a reporting table, every night
- Schedule
0 0 2 * * *→ List activities of Orders, only changed since the last successful run, stored aschanged.csv→ Run a statement onwarehousefor each row ofchanged.csv:insert into hailer_orders (id, name, phase, amount) values ({{_id}}, {{name}}, {{phase}}, {{amount}}::numeric) on conflict (id) do update set name = excluded.name, phase = excluded.phase, amount = excluded.amount. - A Hailer workflow as CSV on SharePoint, every Monday
- Schedule
0 0 7 * * 1→ List activities, stored asorders.csv→ Upload run files*.csvtofinance, folderReports/Hailer. - New Hailer customers into the CRM
- Trigger connected to phase Won of Customers, on moves into the phase → Call the API
crm,POST /contacts, body{"name": "{{name}}", "email": "{{email}}"}→ Change an activity{{id}}: move it to Synced.
Example: weather into PostgreSQL
Install the weather example, create a table create table forecast (time timestamptz primary key, temperature numeric, precipitation numeric, wind numeric) and a warehouse store, add the app weather-demo to it, and add a step after Convert to CSV:
@app.step()
def save_forecast(step: cd.StepInput):
hourly = step.input["hourly"] # from fetch_forecast, passed on by your step
rows = list(zip(hourly["time"], hourly["temperature_2m"], hourly["precipitation"], hourly["wind_speed_10m"]))
cd.db("warehouse").execute_many(
"insert into forecast values (%s, %s, %s, %s) on conflict (time) do update set temperature = excluded.temperature",
rows)
return {"saved": len(rows)}
Monitor
Every run of every workflow, newest first. Used for: answering "did last night's job run?" and finding the run that went wrong.
Filter on one workflow in the list on the left, and search the runs' messages, payloads and step details. Combine words with <<and>> and <<or>>, and group them with parentheses:
(timeout <<or>> error) <<and>> checkout
customer_id <<and>> 7
Click a run to open its details, the same view as on the workflow's Runs tab: each block with status, input, output, logs and files. The trigger column says how it started: manual, webhook, timer, event or hailer. A block that loaded rows shows how many succeeded and failed, and its _failed.csv file is among the run's files.
Example: Amsterdam weather to CSV
A ready-made workflow that uses every part above. Install it, run it, then take it apart.
- Go to Workflows and press Examples, then Install. It creates the app
weather-demo(under Code), your e-mail address as a channel, the action sets Weather report and Workflow problems, and the workflow. Deploying the app takes about a minute. - The workflow opens. Press Run.
- Within a minute you receive an e-mail with the forecast summary and
amsterdam-weather.csvattached. The run also lists the CSV for download.
Removing it
Open Examples again: an installed example shows Installed with Open workflow and Remove. Remove lists what it deletes and asks once more. It deletes exactly what the install created: the workflow with its runs, the two action sets, the app and, when nobody else uses it, the e-mail channel. Things the install reused (an action set or channel you already had) stay. When you changed a part since installing (new code, an edited workflow or action set), the list says so; those changes go too, so copy what you want to keep first. Because it deletes an app, codeDock asks for a code from your authenticator app. Afterwards you can install the example again.
How the data flows
fetch_forecastcalls open-meteo with the coordinates and dates from its parameters and returns the hourly values as its output.check_dataraisescd.StepErrorwhen there are none, which ends the run and fires Workflow problems.to_csvreturns a summary (rows,min_temperature, …) and storescd.File("amsterdam-weather.csv", …)in the run.- "E-mail the CSV" runs no code: it fires Weather report, whose message uses the summary as
{{rows}},{{min_temperature}}and so on, and attaches the run files matching*.csv.
Make it your own
| Where | Parameter | Default |
|---|---|---|
| Workflow (toolbar: Parameters) | latitude, longitude | 52.3676, 4.9041 (Amsterdam) |
| Workflow (toolbar: Parameters) | place | Amsterdam, used in the e-mail |
| Fetch forecast | start_date, end_date | today, today+6; or a date like 2026-11-02 |
| Fetch forecast | hourly | temperature_2m,precipitation,wind_speed_10m |
| Fetch forecast | timezone | Europe/Amsterdam |
| Convert to CSV | file_name | amsterdam-weather.csv |
- For another city, change
place,latitudeandlongitudetogether, for example Utrecht: 52.0907, 5.1214. - To get it every morning, click the trigger and set the schedule to
0 0 6 * * *(06:00 UTC). - To see the failure path, set
start_dateto a date a month ahead: open-meteo refuses it, the step fails and Workflow problems e-mails you the reason. - Change the e-mail's wording in Action sets → Weather report.
- The code is in the app's Editor, or click a block and choose Edit code.
open-meteo.com is free without a key for non-commercial use.
More examples
Common setups, built from the parts above.
A daily report by e-mail
- In an app, write a step that queries your data and returns
cd.StepResult(summary, files=[cd.File("report.csv", …)]). - Create an action set "Daily report" with an e-mail action to the team's recipient and the message
{{rows}} orders yesterday, total € {{total}}. - Make a workflow: trigger → Code block (the step) → Action set block "Daily report" with
*.csvattached. - On the trigger, set the schedule
0 0 6 * * 1-5.
Be told in Slack when anything fails
- Add your Slack incoming webhook as a channel.
- Create an action set "Workflow problems" (severity critical) with a Call webhook action to it and the message
{{workflow}}: {{step}} {{status}}. {{error}} {{run_url}}. - In each workflow, choose it under When the run fails on the trigger, or per block under When the step fails.
Let a monitoring tool raise an alarm
Create an action set "Disk full" with a cooldown of 15 minutes and copy its URL. In your monitoring tool, or a cron job, call it when a threshold is crossed:
pct=$(df --output=pcent / | tail -1 | tr -dc 0-9)
[ "$pct" -gt 90 ] && curl -s "https://notify.<domain>/<id>?host=$(hostname)&pct=$pct"
An order pipeline with an error path
Trigger (event: order.created)
→ Validate order ── error → Action set "Order rejected" (e-mail to sales)
→ Reserve stock ── error → Code "Release reservation"
→ Create invoice (PDF stored as a run file)
→ Action set "Invoice" (e-mail to the customer's recipient, *.pdf attached)
Each step returns what the next needs; raise cd.StepError("out of stock") follows the red arrow. The publishing app only needs cd.publish("order.created", order).
Start a workflow from another system
Turn the trigger's webhook on, store the key in the other system, and call the URL with ?wait=30 to get the last step's output back in the same request: a small API built from blocks.
Command line
codedock does from a terminal what the portal does, which is handy for scripts and CI.
| Command | Does |
|---|---|
codedock login [--api URL] [--token T] | Log in through the browser, or store a token |
codedock init | Create function_app.py, requirements.txt and .codedockignore here |
codedock new NAME [--python 3.13] | Create an app and link this folder to it |
codedock deploy [--no-activate] | Upload this folder, build it and (unless told not to) activate it |
codedock deployments / rollback N | List versions / activate version N again |
codedock logs [--follow] [--tail N] | Read or follow the app's log |
codedock invocations | Recent calls with status and duration |
codedock settings list|set NAME VALUE [--secret]|delete NAME | Manage settings and secrets |
codedock keys list|create NAME [--function FN]|rotate ID|revoke ID | Manage keys |
codedock start | Run the app on your machine at http://127.0.0.1:7071 (no sandbox) |
codedock apps | List your apps |
codedock workflow list|apply FILE|run NAME [--payload JSON] [--wait 60s] | List, create or update from JSON, and run workflows |
codedock workflow runs [NAME] / show RUN_ID | Recent runs / one run with its steps |
codedock workflow webhook NAME [on|off] / delete NAME | Manage the webhook / move to the trash |
Commands that change keys or secrets ask for a code from your authenticator app. Add --app NAME when the folder is not linked to an app.
Quotas and limits
Quotas shows how many apps you have and how many invocations you used this month, against your plan's limits.
| What | Limit |
|---|---|
| Apps per account / functions per app | 3 / 50 (free plan); 25 / 50 (Pro) |
| Invocations per month | 100,000 (free plan); Pro: 2 million included, then billed (see Plans and billing) |
| Request and response body | 6 MB |
| Memory / timeout per call | 128–1024 MB (Pro: 2048 MB) / 1–300 s |
| Workflows per account / runs in progress | 20 / 20 |
| Blocks per workflow | 50 |
| Run payload / step output | 1 MiB / 1 MiB |
| A run's maximum duration | 24 hours |
| Workflow parameters / per block | 50 / 20 |
| Run files | 4 MiB per step, 16 MiB and 20 files per run, kept 7 days |
| Action sets / channels / actions per set | 50 / 50 / 10 |
| Calls per action set URL | 30 per minute |
| Action set e-mails | 200 per account per 24 hours; attachments up to 10 MiB per e-mail |
| Data stores per account | 50 |
| Data store block | 500 rows in the output, 100,000 into a CSV; 8 MiB per file read or written (4 MiB per SharePoint upload); 2 minutes |
| Row by row (for each row, upsert) | 100,000 rows per file; 30 minutes per block; Hailer gets 50 activities per call |
| Hailer | 10,000 activities per list; 50 activities per phase webhook call; one connected phase per workflow |
| Connection tests | 10 per minute |
| Logs and run history | 7 days (Pro: 30 days of logs) |
Every function call, including scheduled runs, events and workflow steps, counts as an invocation. Action set and data store blocks do not.
Plans and billing
Billing is where you choose a plan, see what this month will cost, set a spend cap and find your invoices. Payments go through Mollie, a Dutch payment provider.
| Plan | Price | Included | Beyond that |
|---|---|---|---|
| Free | € 0 | 100,000 invocations a month, 3 apps | Calls are refused until the next month |
| Pro | € 19 a month, excl. VAT | 2 million invocations and 400,000 GB-seconds a month, 25 apps, 2 GB memory, 30 days of logs | € 0.40 per million invocations, € 2.00 per 100,000 GB-seconds |
The prices on the Billing page are the ones that apply; the table shows the starting prices. A GB-second is one second of a function running with 1 GB of memory set for its app: a call of 200 ms with 512 MB is 0.1 GB-s. Durations are rounded up to 100 ms.
Choosing Pro
- Fill in Billing details: company, address, country and the e-mail address for invoices. codeDock sells to businesses. In the EU outside the Netherlands, give your VAT ID; it is checked with the EU's VIES service and your invoices are reverse-charged (no Dutch VAT). Dutch companies pay 21% VAT. Outside the EU no VAT is charged.
- Click Choose Pro. Mollie's checkout opens for the rest of this month's fee (by day), plus VAT. Pay with iDEAL/Wero, Bancontact, SEPA or a card.
- Back in codeDock the plan is active at once and the first invoice is listed as paid. Your payment authorises later charges: by direct debit from the account you paid from (iDEAL, Bancontact), or on the card.
Invoices
On the 1st of each month codeDock invoices the past month's usage beyond what is included, and the fee for the new month, and charges it to your payment method. The invoice is e-mailed and listed under Invoices; View opens it as a page you can print or save as PDF. Next invoice shows what the next one will hold so far.
When a payment fails
You get an e-mail, the page shows a warning, and codeDock tries again 3 and 7 days after the first failure. Pay now pays the open invoice right away, with any method; that also replaces the payment method for later charges. If the invoice is still unpaid after 14 days, the account falls back to the free plan's limits. Nothing is deleted: pay the invoice and choose Pro again.
Spend cap
The most that usage beyond what Pro includes may cost in a month (the € 19 fee does not count). You get an e-mail at 80% and at 100%. When it is reached: keep running (usage is still billed) or refuse invocations until the next month or until you raise the cap. Calls are then answered with 429 quota_exceeded.
Going back to free
Choose Move to Free at month end. Pro stays until the end of the month; the last invoice holds that month's usage and no new fee. Until then, Stay on Pro undoes it. With more than 3 apps, the apps keep running, but you cannot create new ones.
Troubleshooting
- The build failed
- Open the deployment's build log under Deployments. Common causes: a package name in
requirements.txtwithout a Linux wheel, or an error whenfunction_app.pyis imported (such as an invalid schedule). The previous version keeps running. - A call returns 401
- The function needs a key: send
x-functions-key, or make itANONYMOUS. - A call returns 404
not_deployed - The app has no active deployment yet. Deploy it.
- A call returns 500
- Your function raised an exception. The traceback is under Logs; the response's
x-codedock-invocation-idfinds the call under Invocations. - A setting has its old value
- Settings load when an instance starts; deploy again.
- A step cannot be chosen on the canvas
- Only functions with
@app.step()in the app's active deployment are listed. Deploy the app first. - Save stays grey
- Every Code block needs an app and a step function, every Action set block an action set, and every Data store block a store and what it does. Hover Save for the reason.
- No e-mail arrived
- Look in Action sets → Log. "Not confirmed" means the recipient has not opened the confirmation link; suppressed means the cooldown was running; check the spam folder too.
- A file was not attached
- The block's attachment patterns must match the file name, the file must come from the same run, and attachments are at most 10 MiB per e-mail. The e-mail names what was left out.
- A call returns 429
quota_exceeded - On the free plan, this month's 100,000 invocations are used; on Pro, the spend cap is reached and set to refuse invocations. Choose Pro or raise the cap under Billing.
- The VAT ID is refused
- It must start with your country's code (Greece: EL) and be registered for intra-EU trade; check it on the EU's VIES site. "VIES did not answer": a member state's service is down; try again later.
- A payment failed
- See When a payment fails. The e-mail gives the bank's or card's reason; Pay now under Billing pays the open invoice with another method.
- A run file is gone
- Run files are kept for 7 days.
- A run is stuck at running
- Open it in Monitor and look at the block that is still running and its logs. Steps stop at the app's timeout; runs stop after 24 hours.
- Test connection fails
- The message is the server's own. private or internal: the host resolves to a private address; use a public one. did not answer within 20 seconds: a firewall drops codeDock, or the host or port is wrong. password authentication failed or login failed: the user or password. A certificate error: choose Encrypted, certificate not checked only if the server really has a self-signed certificate.
data store "x" is not available to this app- Tick the app under the store's Apps that may use it; new instances start within a minute.
- SharePoint answers 403
- The app registration lacks a Graph application permission with admin consent, or (with
Sites.Selected) access to this site. - Google Drive answers 404
- The folder is not shared with the service account's address shown on the store.
- Hailer refused the sign-in
- Check the e-mail address and password on the store. Users with two-step sign-in cannot be used; give codeDock a user of its own.
- An upsert or a row-by-row block says the run has no file
- The file must come from an earlier block of the same run (a query or list with Also store the rows as CSV, a download); names are exact, including
.csv. - A row-by-row block ends with a warning
- Some rows failed; the others went through. The block's output lists the first 20 reasons with their row numbers, and
<file>_failed.csvin the run has every failed row with anerrorcolumn. Fix the rows (or the mapping) and load that file again; an upsert never creates a second activity for the same match value. - The phase already sends its activities to …
- A Hailer phase calls one webhook address, and another system uses this one. Choose Replace it only if nothing needs that address any more, or use another phase.
- An upsert creates duplicates in Hailer
- The match values differ between the file and Hailer, for example
00123in the file against123in a text field, orA-1againsta-1(matching is exact, apart from spaces at either end). Write codes the same way in both; a number field matches00123and123as the same number. - Hailer does not start the workflow
- Look at the phase in Hailer: its webhook should point at
wf.…/hailer/…with the right events ticked. Connect it again on the trigger if someone changed it. Runs appear in Monitor with trigger hailer. - A Hailer block says a workflow has no field with a key
- The message lists the keys the workflow has. Keys are case-sensitive; set missing keys in Hailer's workflow settings, then reload Workflows and field keys on the store.
- An outgoing call from my code fails
- Only public HTTPS (and HTTP) addresses on IPv4 are reachable; private networks and mail ports are blocked. Set a timeout and log the error.