{"templateId":"../@theme/Templates/BlogWithAuthors","sharedDataIds":{"sidebar":"sidebar-blog/sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":[]},"type":"markdown"},"seo":{"title":"What you need","description":"Developer documentation for the Caraer platform. REST API reference, Bearer authentication, tutorials, and guides for records, forms, webpages, webhooks, and automations.","llmstxt":{"hide":false,"sections":[{"title":"Table of contents","includeFiles":["**/*"],"excludeFiles":[]}],"excludeFiles":[]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This is a start-to-finish guide for building a Caraer app with the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["caraer"," ","CLI"]},". You will scaffold a local project, write a function, validate it, push a"," ","build, and run it against a company."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The Caraer UI is for installing and configuring apps. Authoring — manifest,"," ","functions, webhooks, schedules, inbound routes — happens in a local folder and"," ","is deployed with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["caraer apps push --deploy"]},"."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"what-you-need","__idx":0},"children":["What you need"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Python 3.10 or newer"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["A Caraer account on a company you may develop against (use a sandbox"," ","company, not a customer tenant)"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Permission to create apps:",{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Private"]}," company apps: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["TOOLS_APPS_WRITE"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Public"]}," marketplace apps: global ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Developer"]}," or ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Super admin"]}," (to request a Developer role, email ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"mailto:hello@caraer.com"},"children":["hello@caraer.com"]}," describing the app(s) you want to build and we'll get in touch)"]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"public-or-private","__idx":1},"children":["Public or private"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Decide this before you scaffold. It changes endpoints, validation, and"," ","install."]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":""},"children":[]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Private"},"children":["Private"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Public"},"children":["Public"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Create"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["caraer apps init --private"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["caraer apps init"]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Who can use it"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The creating company (auto-installed on first push)"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Any company after you publish"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Listing fields"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Optional"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["brandmark"]}," plus ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["details"]}," (category, brand color, logo SVG)"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Marketplace review"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Not applicable"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["caraer publish submit"]}]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Keep ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["privateApp: true"]}," in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["caraer.json"]}," for private apps so push and pull use"," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["/api/v2/apps/private*"]},"."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"1-install-the-cli","__idx":2},"children":["1. Install the CLI"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"pipx install caraer-cli\n# or: uv tool install caraer-cli\ncaraer --version\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["From a local checkout: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["./scripts/install.sh"]},". That also installs shell"," ","completion."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If you use Cursor, install the app skill so the agent scaffolds V2 projects"," ","correctly:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"caraer skill install\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"2-sign-in-and-select-a-company","__idx":3},"children":["2. Sign in and select a company"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"caraer auth login\n# CI / password: caraer auth login --email you@example.com\ncaraer company list\ncaraer company select <company-uuid>\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Later commands use the selected company from your profile."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"3-scaffold-a-local-app","__idx":4},"children":["3. Scaffold a local app"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"caraer apps init --name hello_inbox --label \"Hello Inbox\" --auth-method API_KEY\ncd hello_inbox\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For a company-only app:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"caraer apps init --private --name hello_inbox --label \"Hello Inbox\" --auth-method API_KEY\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Every install — ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["API_KEY"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["OAUTH2"]}," — gets a short-lived"," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["installationToken"]}," in webhook, lifecycle, and app-bar payloads. It is an"," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["inst_…"]}," Bearer, valid about one hour, and is ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["not"]}," the long-lived API key."," ","Call Caraer APIs with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Authorization: Bearer"]}," plus that token. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["API_KEY"]}," still"," ","has a separate installer API key (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["hideApiKeyField: true"]}," hides it). Use"," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["OAUTH2"]}," when install must run the Caraer user OAuth flow."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["externalOAuthProviders"]}," is a different thing: Google, Microsoft, and similar"," ","third-party connections. See ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"#external-oauth-and-secrets"},"children":["External OAuth and secrets"]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["init"]}," writes a V2 workspace (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["platformVersion: 2026.2"]},"), a sample function,"," ","lifecycle hooks, and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["src/app/app.caraer.yaml"]},". Do not invent a V1 per-function"," ","GCP layout."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"4-know-the-folder","__idx":5},"children":["4. Know the folder"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"text","header":{"controls":{"copy":{}}},"source":"hello_inbox/\n  caraer.json                 # platformVersion, appUuid, runtime, privateApp\n  package.json                # npm scripts + @caraer/client (Node)\n  src/app/\n    app.caraer.yaml           # identity, auth, settings\n    functions/<name>/         # index.js or main.py\n    shared/                   # helpers shared by every function\n    settings/*.json           # optional modular settings\n    settings-sections/*.json  # optional installer cards\n    lifecycle/*.json          # install | uninstall | rotate | update\n    webhooks/*.json           # platform events → function\n    schedules/*.json          # cron → function\n    inbound/*.json            # public HTTP → function\n","lang":"text"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A folder with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["index.js"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["main.py"]}," is a function named after the folder."," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["function.caraer.json"]}," is only needed to override the entry file or description."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["caraer apps init"]}," puts this line at the top of ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["app.caraer.yaml"]}," so the"," ","editor can load autocomplete and validation from the public schema:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"yaml","header":{"controls":{"copy":{}}},"source":"# yaml-language-server: $schema=https://raw.githubusercontent.com/Caraer-HQ/caraer-app-schemas/main/schemas/app.caraer.schema.json\n","lang":"yaml"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The schemas are public at ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"https://github.com/Caraer-HQ/caraer-app-schemas"},"children":["Caraer-HQ/caraer-app-schemas"]},"."," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["caraer apps validate"]}," also checks against the copies bundled with the CLI."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"5-edit-the-manifest","__idx":6},"children":["5. Edit the manifest"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Open ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["src/app/app.caraer.yaml"]},". Set ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["label"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["name"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["authMethod"]},". For a"," ","public app, replace the placeholder brandmark and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["details"]}," (title,"," ","description, category, logo SVG, brand color) before you submit for review."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Installation settings are for ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["admins installing the app"]},", not for"," ","developers:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Prefer ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SWITCH"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["OBJECT_SINGLE_SELECT"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SINGLE_SELECT"]}," over free text."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Do not ask for ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["caraer_api_base"]}," or other platform URLs. The runtime injects"," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["body.caraerApiBase"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["When the app writes records, declare scopes such as"," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["records.<mapping_field>.all"]}," instead of hard-coding object names. See"," ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"#scopes-macros-and-settings"},"children":["Scopes, macros, and settings"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Group fields with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["settingsSections"]},". Caraer lays cards out automatically"," ","(max three across). Hide advanced fields with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["visibleWhen"]},"."]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"caraer apps add setting inbox_label --type SINGLE_LINE --label \"Inbox label\"\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"6-write-a-function","__idx":7},"children":["6. Write a function"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Node (default):"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"js","header":{"controls":{"copy":{}}},"source":"exports.handler = async (req, res) => {\n  const body = req.body || {};\n  return res.status(200).json({ ok: true, event: body.event || null });\n};\n","lang":"js"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Python:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"def handler(request):\n    body = request.get(\"body\") or {}\n    return {\"statusCode\": 200, \"body\": {\"ok\": True, \"event\": body.get(\"event\")}}\n","lang":"python"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The platform sends a JSON envelope on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["req.body"]}," (Node) or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["request[\"body\"]"]}," ","(Python). A ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["record.candidate.created"]}," webhook (USER_FRIENDLY, the CLI"," ","default) looks"," ","like this after Caraer merges the installation fields:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"event\": {\n    \"type\": \"Created\",\n    \"timestamp\": 1774272000000,\n    \"correlationId\": \"8f2c1a0e-4b3d-4c9a-9e1f-2d6b0c8a7f11\"\n  },\n  \"user\": {\n    \"type\": \"user\",\n    \"uuid\": \"0b1c2d3e-4f56-7890-abcd-ef1234567890\",\n    \"email\": \"ada@example.com\",\n    \"firstname\": \"Ada\",\n    \"lastname\": \"Lovelace\",\n    \"companyUuid\": \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\"\n  },\n  \"context\": {\n    \"companyUuid\": \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\"\n  },\n  \"record\": {\n    \"record\": {\n      \"uuid\": \"11111111-2222-3333-4444-555555555555\",\n      \"objectName\": \"candidate\",\n      \"properties\": {\n        \"first_name\": \"Ada\",\n        \"email\": \"ada@example.com\"\n      }\n    },\n    \"relations\": {}\n  },\n  \"appUuid\": \"99999999-aaaa-bbbb-cccc-dddddddddddd\",\n  \"companyUuid\": \"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\",\n  \"companyName\": \"Example BV\",\n  \"installationToken\": \"…\",\n  \"caraerApiBase\": \"https://api.caraer.com/api\",\n  \"settingsSchema\": [\n    { \"name\": \"inbox_label\", \"type\": \"SINGLE_LINE\", \"value\": \"Main\" }\n  ],\n  \"scopes\": [\"records.candidate.read\"],\n  \"secrets\": {},\n  \"connections\": []\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Call Caraer APIs with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Authorization: Bearer"]}," plus ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["body.installationToken"]}," ","(short-lived ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["inst_…"]}," token, not the API key) and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["body.caraerApiBase"]}," (no"," ","trailing slash). Flatten ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["settingsSchema"]}," to"," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["name → value"]},". Topic-specific data lives on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["event"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["record"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["user"]},", and"," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["context"]}," — not a separate ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["payload"]}," wrapper."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Lifecycle hooks (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Installed"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Updated"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Uninstalled"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Rotated"]},") use a"," ","flatter envelope: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["event"]}," is a string, and there is no ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["record"]}," object."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Prefer the Caraer SDKs over hand-rolled ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fetch"]}," / ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["urllib"]}," calls. They are"," ","generated from the production OpenAPI spec and include both the HTTP client"," ","and webhook payload types (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["LifecyclePayload"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["WebhookPayload"]},"). Node"," ","scaffolds already add ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@caraer/client"]}," as a dependency."]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Runtime"},"children":["Runtime"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Package"},"children":["Package"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Install"},"children":["Install"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Source"},"children":["Source"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Node"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"a","attributes":{"href":"https://www.npmjs.com/package/@caraer/client"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["@caraer/client"]}]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["npm install @caraer/client"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"a","attributes":{"href":"https://github.com/Caraer-HQ/caraer-js-client"},"children":["Caraer-HQ/caraer-js-client"]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Python"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"a","attributes":{"href":"https://pypi.org/project/caraer-client/"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["caraer-client"]}]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pip install caraer-client"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"a","attributes":{"href":"https://github.com/Caraer-HQ/caraer-python-client"},"children":["Caraer-HQ/caraer-python-client"]}]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["API reference: ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"https://developer.caraer.com"},"children":["developer.caraer.com"]},". OpenAPI:"," ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"https://v2.api.caraer.com/api-docs.yaml"},"children":["v2.api.caraer.com/api-docs.yaml"]},"."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"ts","header":{"controls":{"copy":{}}},"source":"import type { WebhookPayload } from \"@caraer/client\";\n","lang":"ts"},"children":[]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"python","header":{"controls":{"copy":{}}},"source":"from caraer_client import WebhookPayload\n","lang":"python"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Put shared code in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["src/app/shared/"]}," and import it with the same relative path"," ","locally and in production:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"js","header":{"controls":{"copy":{}}},"source":"const { flattenSettings } = require(\"../../shared\");\n","lang":"js"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["V2 apps deploy one container per app. Function code in the Caraer UI is"," ","rejected for build-deployed apps. Always change code locally and push again."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"7-wire-a-trigger","__idx":8},"children":["7. Wire a trigger"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["init"]}," already created lifecycle hooks under ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["src/app/lifecycle/"]}," that call"," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["on-install"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["on-update"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["on-uninstall"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["on-rotate"]},". Add the trigger"," ","you actually need:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"caraer apps add function catch-created\ncaraer apps add webhook --topic record.candidate.created --function catch-created\ncaraer apps add schedule heartbeat --function catch-created --cron \"0 0 */12 * * *\"\ncaraer apps add inbound provider-push --function catch-created --auth SHARED_SECRET\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Record webhooks are always object-scoped:"," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["record.{object}.{created|updated|deleted|all}"]},". There is no company-wide"," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["record.created"]}," topic."]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Trigger"},"children":["Trigger"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Use when"},"children":["Use when"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Webhook (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["record.candidate.created"]},", …)"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["A record event on that object should run your code"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Inbound"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["An external system POSTs to a public URL"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Schedule"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Cron (Spring 5–6 field expression)"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Lifecycle"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Install, settings save, uninstall, or token rotate"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["App bar"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["A button on a record, a trait tab, or a sidebar tool. See ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"/blog/2026-08-23-app-bars-in-caraer-apps"},"children":["App bars in Caraer apps"]}]}]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"inbound-url-and-params","__idx":9},"children":["Inbound URL and params"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["There is no copyable inbound URL in the Caraer app today. After deploy, assemble"," ","it from the API host, the app UUID (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["caraer.json"]}," / ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["caraer apps select"]},"), the"," ","route ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["name"]}," in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["src/app/inbound/<name>.json"]},", and the installing company UUID"," ","(",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["caraer company list"]},"):"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"http","header":{"controls":{"copy":{}}},"source":"POST {caraerApiBase}/v2/public/apps/{appUuid}/inbound/{routeName}?companyUuid={companyUuid}\nContent-Type: application/json\nX-Caraer-Inbound-Secret: <shared-secret>\n","lang":"http"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["companyUuid"]}," can also be sent as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["X-Caraer-Company-Uuid"]}," instead of a query"," ","param. For ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SHARED_SECRET"]}," routes, send the secret in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["X-Caraer-Inbound-Secret"]},"."," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["validate"]}," warns if ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sharedSecret"]}," is missing."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Pass data in the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["JSON body"]},". The function receives:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"action\": \"app.inbound\",\n  \"inboundRoute\": \"provider-push\",\n  \"body\": { \"hello\": \"caraer\" }\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Query params other than ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["companyUuid"]}," are not forwarded. Auth headers are used"," ","only to verify the caller."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Locally, ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["caraer apps local dev"]}," prints"," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST http://127.0.0.1:8787/inbound/<routeName>"]}," and accepts the same JSON"," ","body."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["enqueue: true"]}," (the default) returns ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["202"]}," and runs the function as a job."," ","Long work should enqueue and return quickly."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"8-validate","__idx":10},"children":["8. Validate"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["After every structural edit:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"caraer apps validate\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Fix errors before you push. Warnings (for example a missing inbound"," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["sharedSecret"]},") can wait for the installer when you document them."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"9-run-it-locally","__idx":11},"children":["9. Run it locally"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"caraer apps local dev\ncaraer apps local test --function hello-world --sample-only\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["local dev"]}," serves ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST /functions/<name>"]}," and emulates installation"," ","state, secrets, and jobs. It does not replace a deployed install for OAuth"," ","or real record events."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"10-push-and-deploy","__idx":12},"children":["10. Push and deploy"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"caraer apps push --dry-run\ncaraer apps push --deploy\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The first unlinked push creates the remote app, then uploads a build archive"," ","and waits until ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["runtimeStatus"]}," is ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["READY"]}," or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["FAILED"]},". You will be asked for"," ","a semver greater than the previous build and optional release notes."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["push"]}," syncs the manifest, functions, webhooks, schedules, inbound routes, and"," ","external OAuth providers. There is no separate upload command."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Root ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".env"]}," is packed into the runtime as environment variables"," ","(",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["AFFINDA_API_KEY"]},", …). It is not stored on the persisted build manifest. Do"," ","not commit ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".env"]},". Expand ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["${ENV_VAR}"]}," OAuth client fields from the process"," ","environment on push."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Check status:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"caraer apps status\ncaraer apps release version\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"11-install-and-use-it","__idx":13},"children":["11. Install and use it"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Private apps"]}," are installed for the creating company when they are"," ","created. Open ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Settings → Apps"]}," in Caraer to configure settings."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Public apps"]}," are installed from the App store in Caraer for each company"," ","that should use them. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["caraer publish submit"]}," starts marketplace review;"," ","private apps cannot be submitted."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Then:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"caraer apps state get\ncaraer apps secrets list\ncaraer apps local logs --all --follow\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To update: edit locally, ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["validate"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["push --dry-run"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["push --deploy"]},". Do not"," ","hand-write remote UUIDs into YAML — push fills them."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"scopes-macros-and-settings","__idx":14},"children":["Scopes, macros, and settings"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The installation token is only half of API access. Every call the function"," ","makes as that token is checked against the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["scopes granted on the install"]},"."," ","You declare what the app needs in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["requiredScopes"]},". The installer can still"," ","uncheck items on the consent / install screen."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"macro-scopes","__idx":15},"children":["Macro scopes"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Write macros, not every leaf scope. Caraer expands them against the tenant"," ","catalog at install time (and again when settings change)."]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Macro"},"children":["Macro"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Expands to"},"children":["Expands to"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["tools.forms.all"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Every ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["tools.forms.*"]}," scope"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["records.candidate.all"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Record-level scopes for ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["candidate"]}," (create / read / update / delete). Does ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["not"]}," include property or relation scopes"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["records.candidate.properties_all"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Every ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["records.candidate.property.*"]}," scope"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["records.candidate.relations_all"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Every ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["records.candidate.relation.*"]}," scope"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["records.*.all"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Record-level scopes for ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["every"]}," object. Use sparingly"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["global.all"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Every ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["global.*"]}," scope"]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A typical “this app fully owns one object” pack is the three ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["records.<object>.*"]}," macros together. The CLI wizard can add that pack in one step."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Concrete scopes (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["tools.apps.read"]},") are fine when you need one permission only."," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["tools.objects_schemas.write"]}," is the usual extra if install creates objects."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"bind-scopes-to-settings","__idx":16},"children":["Bind scopes to settings"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Do not hard-code ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["records.candidate.all"]}," when the installer chooses the object."," ","Put the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["setting name"]}," in angle brackets. Caraer substitutes the selected"," ","object when settings are saved:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"yaml","header":{"controls":{"copy":{}}},"source":"settingsSchema:\n  - name: attendee_object\n    label: Attendee object\n    type: OBJECT_SINGLE_SELECT\n    required: true\n  - name: candidate_mapping\n    label: Candidate\n    type: MAPPING\n    required: true\n\nrequiredScopes:\n  - tools.objects_schemas.write\n  - records.<attendee_object>.all\n  - records.<attendee_object>.properties_all\n  - records.<attendee_object>.relations_all\n  - records.<candidate_mapping>.all\n  - records.<candidate_mapping>.properties_all\n  - records.<candidate_mapping>.relations_all\n","lang":"yaml"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["How substitution works:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["<fieldName>"]}," must match a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["settingsSchema"]}," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["name"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["OBJECT_SINGLE_SELECT"]}," / ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["OBJECT_MULTI_SELECT"]}," use the selected object name"," ","(multi-select expands to one scope set per object)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["MAPPING"]}," uses ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["mappingValue.objectName"]},", not the mapping items."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["An empty or unset field grants ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["no"]}," extra record scopes."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Changing the setting updates the install: scopes for the old object are"," ","removed, scopes for the new object are added."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The function still sees the setting value on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["body.settingsSchema"]},". Use that"," ","value as the object name in API calls; the token is already scoped to it."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"settings-ux","__idx":17},"children":["Settings UX"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Settings are for ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["admins installing the app"]},", not for you. Keep required"," ","fields to the minimum that makes the app work."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Prefer ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SWITCH"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["OBJECT_SINGLE_SELECT"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["SINGLE_SELECT"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["MAPPING"]}," over"," ","free-text object or property names."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Group related fields with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["settingsSections"]}," (or"," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["src/app/settings-sections/*.json"]},"). Caraer lays cards left-to-right, max"," ","three across. Unassigned fields land in ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Other settings"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Hide advanced fields behind a switch:"]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"yaml","header":{"controls":{"copy":{}}},"source":"- name: custom_mapping\n  type: SWITCH\n  defaultValue: false\n- name: field_mapping\n  type: MAPPING\n  visibleWhen:\n    - field: custom_mapping\n      operator: EQUALS\n      value: true\n","lang":"yaml"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Hidden fields are not required and their values are dropped."]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["valueScope: USER"]}," stores the value per Caraer user (calendar picks). Default"," ","is ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["COMPANY"]}," (one value for the install)."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["FILE"]}," / ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["MULTI_FILE"]}," belong on app-bar action dialogs, not as everyday"," ","install settings."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"function-habits","__idx":18},"children":["Function habits"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Return quickly from webhooks and inbound handlers. Enqueue"," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST {caraerApiBase}/v2/apps/{appUuid}/installation/jobs"]}," for work that can"," ","exceed the 60s function timeout."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Set ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["waitUntilComplete: true"]}," on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["lifecycle/install.json"]}," (and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["update.json"]}," ","if settings save must show hook-written mappings) so the install request"," ","waits for the function."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Put shared helpers in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["src/app/shared/"]}," and import"," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["require(\"../../shared\")"]}," from a function folder. Do not copy files."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Read third-party tokens from ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["body.secrets"]}," / ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["body.connections"]},". Root ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".env"]}," ","is for ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["your"]}," deploy-time keys (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GOOGLE_CLIENT_SECRET"]},"), not installer"," ","secrets. See ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"#external-oauth-and-secrets"},"children":["External OAuth and secrets"]},"."]}]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"external-oauth-and-secrets","__idx":19},"children":["External OAuth and secrets"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["authMethod"]}," is how the ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["company installs the Caraer app"]},"."," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["externalOAuthProviders"]}," is how an installer (or each user) connects a"," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["third-party account"]}," such as Google Calendar. They are independent: a"," ","typical sync app is ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["authMethod: API_KEY"]}," plus one or more external"," ","providers."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["There are also two kinds of secret:"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Kind"},"children":["Kind"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Where"},"children":["Where"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Who sees it"},"children":["Who sees it"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Example"},"children":["Example"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Deploy-time env"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Root ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".env"]},", packed into the container"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Your function as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["process.env"]}," / ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["os.environ"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GOOGLE_CLIENT_SECRET"]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Installation vault"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Encrypted on the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["HAS_APP"]}," install"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Injected on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["body.secrets"]}," at invoke time"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["gmail_access_token"]},", your own API keys"]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Do not commit ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".env"]},". Do not put OAuth client secrets in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["settingsSchema"]},"."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"declare-a-provider","__idx":20},"children":["Declare a provider"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"yaml","header":{"controls":{"copy":{}}},"source":"externalOAuthProviders:\n  - name: gmail\n    label: Google Calendar\n    connectionOwner: USER          # or COMPANY (default)\n    authorizeUrl: https://accounts.google.com/o/oauth2/v2/auth\n    tokenUrl: https://oauth2.googleapis.com/token\n    clientId: ${GOOGLE_CLIENT_ID}\n    clientSecret: ${GOOGLE_CLIENT_SECRET}\n    scopes:\n      - https://www.googleapis.com/auth/calendar.events\n      - https://www.googleapis.com/auth/calendar.readonly\n      - https://www.googleapis.com/auth/userinfo.email\n","lang":"yaml"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["${ENV_VAR}"]}," fields expand on ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["caraer apps push"]}," from the process environment"," ","or root ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".env"]},". The CLI never writes the raw client secret into the persisted"," ","manifest."]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"connectionOwner"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["connectionOwner"]}]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Who connects"},"children":["Who connects"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Tokens on invoke"},"children":["Tokens on invoke"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["COMPANY"]}," (default)"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["One shared account per install"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["body.secrets.gmail_access_token"]}," / ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["gmail_refresh_token"]},", and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["body.connections[]"]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["USER"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["One account per Caraer user"]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["body.connections[]"]}," (plus connection-scoped keys in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["body.secrets"]},")"]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Connect is ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["not"]}," a lifecycle webhook. The installer uses the Connect button"," ","in Caraer, or your code starts it:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"http","header":{"controls":{"copy":{}}},"source":"POST {caraerApiBase}/v2/apps/{appUuid}/installation/oauth/{provider}/start?redirectUri=...\nAuthorization: Bearer <installationToken>\n","lang":"http"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["That returns ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{ \"authorizeUrl\" }"]},". Open it in the browser. After callback,"," ","Caraer stores tokens in the vault and refreshes them before the next invoke"," ","when they are close to expiry."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Status and revoke:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"http","header":{"controls":{"copy":{}}},"source":"GET    {caraerApiBase}/v2/apps/{appUuid}/installation/connections\nDELETE {caraerApiBase}/v2/apps/{appUuid}/installation/connections/{providerOrConnectionId}\n","lang":"http"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["body.connections[]"]}," looks like:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"id\": \"…\",\n  \"provider\": \"gmail\",\n  \"ownerType\": \"USER\",\n  \"ownerUserUuid\": \"…\",\n  \"accountLabel\": \"ada@example.com\",\n  \"connectedAt\": 1774272000000,\n  \"accessToken\": \"ya29.…\"\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Connection-scoped vault keys (always present after connect):"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["oauth.{provider}.{connectionId}.access_token"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["oauth.{provider}.{connectionId}.refresh_token"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["oauth.{provider}.{connectionId}.access_token_expires_at"]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["COMPANY"]}," connections Caraer also mirrors the legacy aliases"," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{provider}_access_token"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{provider}_refresh_token"]},", and"," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{provider}_access_token_expires_at"]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Per-user picks (which calendars to sync) belong in settings with"," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["valueScope: USER"]},", saved via"," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PUT {caraerApiBase}/v2/apps/{appUuid}/installation/settings/user"]},". That"," ","fires ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["app.updated"]}," with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["userSettingsChanged: true"]},"."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"installation-secrets-vault","__idx":21},"children":["Installation secrets vault"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Use the vault for tokens and keys the function needs at runtime that are"," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["not"]}," your OAuth client credentials."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Caraer injects the decrypted map as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["body.secrets"]}," on webhook, lifecycle,"," ","and app-bar invokes (empty object when none are set). The HTTP API is"," ","write-oriented so a leaked Bearer cannot dump values:"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Method"},"children":["Method"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Path"},"children":["Path"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Returns"},"children":["Returns"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["/v2/apps/{appUuid}/installation/secrets"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Secret ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["names"]}," only"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PUT"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["/v2/apps/{appUuid}/installation/secrets/{name}"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Body ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{ \"value\": \"…\" }"]}]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["DELETE"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["/v2/apps/{appUuid}/installation/secrets/{name}"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["—"]}]}]}]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"caraer apps secrets list\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Cap is 64 secrets per install. Prefer the injected ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["body.secrets"]}," in a"," ","handler; call ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PUT"]}," when you need to persist a token you obtained yourself"," ","(for example a provider that is not an ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["externalOAuthProviders"]}," entry)."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Related install APIs (same Bearer):"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Method"},"children":["Method"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Path"},"children":["Path"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Purpose"},"children":["Purpose"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GET"]}," / ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["PUT"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["/v2/apps/{appUuid}/installation/state"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Cursors, watermarks (max 256KB)"]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["POST"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["/v2/apps/{appUuid}/installation/jobs"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["{ \"functionName\", \"payload\", \"delaySeconds\"? }"]}]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["local dev"]}," emulates state, secrets, and jobs. It does not run the real"," ","provider OAuth callback."]},{"$$mdtype":"Tag","name":"hr","attributes":{},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"what-not-to-do","__idx":22},"children":["What not to do"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Invent V1 per-function Cloud Function layouts (",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["platformVersion: 2026.1"]},")"," ","unless you are maintaining a legacy app."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Copy helper files into every function folder. Use ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["src/app/shared/"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Put developer-only knobs (API base URLs, feature flags for you) in"," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["settingsSchema"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Hard-code ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["records.candidate.all"]}," when the installer picks the object. Use"," ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["records.<setting_name>.all"]}," instead."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Request ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["records.*.all"]}," (or the property/relation wildcards) unless the app"," ","truly needs every object in the tenant."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Skip ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["validate"]}," before ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["push"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Edit function code in the Caraer UI on a build-deployed app."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Commit ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":[".env"]}," or put raw ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["clientSecret"]}," values in YAML. Use ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["${ENV_VAR}"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Treat ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["authMethod: OAUTH2"]}," as the way to connect Google. That is Caraer"," ","user consent. Third-party accounts are ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["externalOAuthProviders"]},"."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"related-reading","__idx":23},"children":["Related reading"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"a","attributes":{"href":"/blog/2026-03-25-serverless-functions-in-caraer-apps"},"children":["Serverless functions in Caraer apps"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"a","attributes":{"href":"/blog/2026-08-23-app-bars-in-caraer-apps"},"children":["App bars in Caraer apps"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"a","attributes":{"href":"/apis/cli"},"children":["Caraer CLI"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"a","attributes":{"href":"/blog/2026-03-25-caraer-data-model-object-property-relation"},"children":["Caraer data model: Object, Property, Relation"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Node SDK: ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"https://www.npmjs.com/package/@caraer/client"},"children":["@caraer/client"]}," (",{"$$mdtype":"Tag","name":"a","attributes":{"href":"https://github.com/Caraer-HQ/caraer-js-client"},"children":["source"]},")"]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Python SDK: ",{"$$mdtype":"Tag","name":"a","attributes":{"href":"https://pypi.org/project/caraer-client/"},"children":["caraer-client"]}," (",{"$$mdtype":"Tag","name":"a","attributes":{"href":"https://github.com/Caraer-HQ/caraer-python-client"},"children":["source"]},")"]}]}]},"headings":[{"value":"What you need","id":"what-you-need","depth":2},{"value":"Public or private","id":"public-or-private","depth":2},{"value":"1. Install the CLI","id":"1-install-the-cli","depth":2},{"value":"2. Sign in and select a company","id":"2-sign-in-and-select-a-company","depth":2},{"value":"3. Scaffold a local app","id":"3-scaffold-a-local-app","depth":2},{"value":"4. Know the folder","id":"4-know-the-folder","depth":2},{"value":"5. Edit the manifest","id":"5-edit-the-manifest","depth":2},{"value":"6. Write a function","id":"6-write-a-function","depth":2},{"value":"7. Wire a trigger","id":"7-wire-a-trigger","depth":2},{"value":"Inbound URL and params","id":"inbound-url-and-params","depth":3},{"value":"8. Validate","id":"8-validate","depth":2},{"value":"9. Run it locally","id":"9-run-it-locally","depth":2},{"value":"10. Push and deploy","id":"10-push-and-deploy","depth":2},{"value":"11. Install and use it","id":"11-install-and-use-it","depth":2},{"value":"Scopes, macros, and settings","id":"scopes-macros-and-settings","depth":2},{"value":"Macro scopes","id":"macro-scopes","depth":3},{"value":"Bind scopes to settings","id":"bind-scopes-to-settings","depth":3},{"value":"Settings UX","id":"settings-ux","depth":3},{"value":"Function habits","id":"function-habits","depth":3},{"value":"External OAuth and secrets","id":"external-oauth-and-secrets","depth":2},{"value":"Declare a provider","id":"declare-a-provider","depth":3},{"value":"Installation secrets vault","id":"installation-secrets-vault","depth":3},{"value":"What not to do","id":"what-not-to-do","depth":2},{"value":"Related reading","id":"related-reading","depth":2}],"frontmatter":{"title":"How to create a Caraer app","authors":["sem_tadema"],"tags":["documentation","app","tutorial"],"template":"../@theme/Templates/BlogWithAuthors","seo":{"title":"What you need"}},"lastModified":"2026-08-23T14:27:46.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/blog/2026-08-23-how-to-create-a-caraer-app","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}