diff --git a/.claude/commands/add-model.md b/.claude/commands/add-model.md new file mode 100644 index 00000000000..1fcf828537c --- /dev/null +++ b/.claude/commands/add-model.md @@ -0,0 +1,159 @@ +--- +description: Add a new LLM model to apps/sim/providers/models.ts with specs verified against the provider's live API docs (no hallucination) +argument-hint: [docs-url] +--- + +# Add Model Skill + +You add a new model entry to `apps/sim/providers/models.ts`. **Every numeric and capability claim MUST be derived from a live web fetch of the provider's official docs in this session.** Marketing emails, training data, and your prior knowledge are not sources of truth — they routinely hallucinate pricing, context windows, and capability lists. + +## Hard rules (do not skip) + +1. **Live-fetch or refuse.** Before writing the entry, you must successfully WebFetch the provider's official models/pricing page in this session. If you cannot reach an authoritative source for any field, **mark the field as UNVERIFIED in your report and ask the user before guessing**. Never fill in pricing or capabilities from memory. +2. **Two-source rule for pricing.** Cross-check input/output/cached pricing against at least one secondary source (OpenRouter, Artificial Analysis, CloudPrice, mem0, intuitionlabs). If sources disagree, the provider's own docs win — but flag the disagreement. +3. **Read the code before setting capability flags.** Capability flags are dead unless the provider's implementation under `apps/sim/providers/{provider}/` actually consumes them (see Consumption Matrix below). Setting a flag the provider ignores is a silent bug. +4. **Cite every fact.** Your final report must list the URL each value came from. No URL → not verified. + +## Your Task + +1. Identify provider and model id from user args +2. Live-fetch official docs + pricing page + capability/parameter pages + at least one secondary source +3. Apply the Consumption Matrix to know which capability flags are real +4. Read 2-3 sibling entries in `models.ts` and match their pattern exactly +5. Insert the entry, run `bun run lint`, print the verification report + +## Step 1: Live source-of-truth lookup + +In priority order — fetch all that exist for the provider: + +| Provider | Models index | Pricing | Reasoning/parameter caveats | +|---|---|---|---| +| OpenAI | platform.openai.com/docs/models | openai.com/api/pricing | platform.openai.com/docs/guides/reasoning | +| Anthropic | docs.anthropic.com/en/docs/about-claude/models | anthropic.com/pricing | docs.anthropic.com/en/docs/build-with-claude/extended-thinking | +| Google (Gemini) | ai.google.dev/gemini-api/docs/models | ai.google.dev/pricing | ai.google.dev/gemini-api/docs/thinking | +| xAI | docs.x.ai/developers/models | docs.x.ai/developers/models (per-model detail page) | docs.x.ai/developers/model-capabilities/text/reasoning | +| Mistral | docs.mistral.ai/getting-started/models/models_overview | mistral.ai/pricing | n/a | +| DeepSeek | api-docs.deepseek.com/quick_start/pricing | same | api-docs.deepseek.com/guides/reasoning_model | +| Groq | console.groq.com/docs/models | groq.com/pricing | n/a | +| Cerebras | inference-docs.cerebras.ai/models | cerebras.ai/pricing | n/a | + +Secondary verification (use at least one): `openrouter.ai//`, `artificialanalysis.ai/models/`, `cloudprice.net/models/-`. + +Use a precise WebFetch prompt: *"Extract for {model_id}: exact model id string, context window in tokens, input price per 1M, cached input price per 1M, output price per 1M, max output tokens, supported reasoning effort levels, accepted parameters (temperature, top_p), release date. Do not fill in fields you cannot find."* + +## Step 2: Consumption Matrix (which provider honors which capability) + +| Capability | Honored by | Effect if set elsewhere | +|---|---|---| +| `temperature` | All providers (passed through if set) | Safe but inert on always-reasoning models that reject it | +| `toolUsageControl` | All providers (provider-level, not per-model) | n/a — set on `ProviderDefinition`, not models | +| `reasoningEffort` | `openai/core.ts`, `azure-openai`, `anthropic/core.ts` (mapped to thinking), `gemini/core.ts` | **Dead on xai, deepseek, mistral, groq, cerebras, openrouter, fireworks, bedrock, vertex** unless their core consumes it — re-grep before assuming | +| `verbosity` | `openai/core.ts`, `azure-openai/index.ts` only | Dead elsewhere | +| `thinking` | `anthropic/core.ts`, `gemini/core.ts` | Dead elsewhere | +| `nativeStructuredOutputs` | `anthropic/core.ts`, `fireworks/index.ts`, `openrouter/index.ts` | Dead on openai, xai, google, vertex, bedrock, azure-openai, deepseek, mistral, groq, cerebras | +| `maxOutputTokens` | Read by UI + executor for token estimation | Always meaningful — set if provider documents a cap | +| `computerUse` | `anthropic/core.ts` | Dead elsewhere | +| `deepResearch` | UI flag for routing to deep-research SKUs | Set only on actual deep-research model IDs | +| `memory: false` | Conversation persistence opt-out | Set only when model genuinely cannot maintain history (e.g., deep-research) | + +**Always re-grep before relying on this table** — the codebase moves: + +```bash +rg "reasoningEffort|reasoning_effort" apps/sim/providers// +rg "verbosity" apps/sim/providers// +rg "request\.thinking|thinking:" apps/sim/providers// +rg "supportsNativeStructuredOutputs|nativeStructuredOutputs" apps/sim/providers// +``` + +## Step 3: Match the provider's existing entry pattern + +Open `apps/sim/providers/models.ts`, find `PROVIDER_DEFINITIONS[].models`, read 2-3 sibling entries. Match field order exactly: + +```ts +{ + id: '', + pricing: { + input: , + cachedInput: , // omit if provider doesn't offer caching + output: , + updatedAt: '', + }, + capabilities: { + // only flags the provider actually consumes — see matrix + }, + contextWindow: , + releaseDate: '', + recommended: true, // only if new flagship; ask user before swapping + speedOptimized: true, // only on smallest/fastest tier + deprecated: true, // only on retired models +} +``` + +### Reseller providers (azure-openai, azure-anthropic, vertex, bedrock, openrouter) + +Model id MUST be prefixed: `azure/`, `azure-anthropic/`, `vertex/`, `bedrock/`, `openrouter/`. Pricing usually mirrors the upstream provider but verify on the reseller's own pricing page. + +### Insertion order + +Within a family, newest first (matches existing convention: GPT-5.5 above GPT-5.4 above GPT-5.2). Across families, biggest/flagship at top of list. + +### `recommended` / `speedOptimized` + +- At most one or two `recommended: true` per provider — the current flagship(s). +- If you're adding a new flagship, ask the user before removing `recommended` from the previous flagship. Never silently flip it. +- `speedOptimized: true` only on the smallest/fastest tier (nano, flash-lite, haiku class). + +## Step 4: Write, lint + +```bash +bun run lint +``` + +Lint must pass before reporting done. **If lint fails:** read the error, fix the syntax/typing issue in the entry you just wrote (do not delete the entry — it's the work product), re-run lint, and note the fix in a "Lint adjustments" line in the verification report. Never report done with lint failing. + +## Step 5: Verification report (mandatory format) + +End with this exact structure: + +```markdown +### Verification — + +| Field | Value | Source URL | Status | +|---|---|---|---| +| `id` | `grok-4.3` | https://docs.x.ai/... | ✓ verified | +| `contextWindow` | 1,000,000 | https://docs.x.ai/... + https://openrouter.ai/... | ✓ verified (2 sources agree) | +| `input` | $1.25/M | https://docs.x.ai/... | ✓ verified | +| `cachedInput` | $0.20/M | https://cloudprice.net/... | ⚠️ single source | +| `output` | $2.50/M | https://docs.x.ai/... + https://openrouter.ai/... | ✓ verified | +| `capabilities.temperature` | `{ min: 0, max: 1 }` | matches sibling entries | — pattern-match only | +| `capabilities.reasoningEffort` | NOT SET | provider docs say API rejects it for this model | ✓ correctly omitted | +| `releaseDate` | 2026-04-30 | https://docs.x.ai/... announcement | ✓ verified | + +**Disagreements** +- _none_ OR _OpenRouter says X, provider docs say Y — used Y per provider rule_ + +**Unverified fields** +- _none_ OR _: could not find authoritative source — left as based on sibling pattern; please confirm_ +``` + +If any row is ⚠️ single-source or "unverified," **state it plainly to the user and ask whether to proceed**. Do not silently merge. + +## What to do if you cannot find a source + +Omitting a field is **not the same as verifying it**. Any field you cannot confirm from a live fetch must be **both** omitted from the entry **and** listed as ❓ UNVERIFIED in the report's "Unverified fields" section, with the URLs you attempted. Then ask the user to confirm before merging. + +- Pricing missing → do NOT guess. Omit `cachedInput`. Mark ❓ UNVERIFIED. Ask the user for the price or the docs URL. +- Context window missing → do NOT guess. Ask the user; mark ❓ UNVERIFIED. +- Release date missing → omit the field; mark ❓ UNVERIFIED in the report. +- Capability uncertain → omit the flag (safer than setting a dead/wrong one); mark ❓ UNVERIFIED so the user knows you didn't confirm it either way. + +## Anti-patterns this skill exists to prevent + +- ❌ Trusting a marketing email (xAI's grok-4.3 email claimed "3 reasoning efforts" but the API rejects `reasoning_effort` — verified by official docs only) +- ❌ Setting `nativeStructuredOutputs: true` on xai/openai/google (dead — only anthropic/fireworks/openrouter consume it) +- ❌ Setting `thinking` on non-Anthropic/non-Gemini providers +- ❌ Setting `verbosity` on anything other than OpenAI gpt-5.x +- ❌ Copying `pricing.updatedAt` from a sibling instead of using today's date +- ❌ Inventing a `cachedInput` price by dividing input by 4 (varies by provider — find an explicit number) +- ❌ Stamping `recommended: true` on the new model without removing it from the previous flagship +- ❌ Reporting "done" with any UNVERIFIED row in the table diff --git a/.claude/commands/validate-model.md b/.claude/commands/validate-model.md new file mode 100644 index 00000000000..10c6aaa0b27 --- /dev/null +++ b/.claude/commands/validate-model.md @@ -0,0 +1,166 @@ +--- +description: Validate a model entry (or every model in a provider) in apps/sim/providers/models.ts against the provider's live API docs (no hallucination — reports what cannot be verified) +argument-hint: [model-id] +--- + +# Validate Model Skill + +You audit one or more model entries in `apps/sim/providers/models.ts` against the provider's official live API docs. **Hallucinated pricing and capabilities are the #1 failure mode in this file.** Every numeric and capability claim must be re-derived from a live web fetch in this session — not from memory, not from training data, not from the user's marketing email. + +## Hard rules (do not skip) + +1. **Live-fetch or report unverified.** Each field must be backed by a live WebFetch in this session. If you cannot reach an authoritative URL for a field, mark it **UNVERIFIED** in the report — do not silently confirm it from memory. +2. **Cite every fact.** Every value in the report must show the source URL it was checked against. No URL → mark UNVERIFIED. +3. **Two-source rule for pricing.** Cross-check input/output/cached against at least one secondary source (OpenRouter, Artificial Analysis, CloudPrice). If sources disagree, the provider's own docs win — flag the disagreement. +4. **Inspect provider implementation before flagging capability mismatches.** A capability flag in `models.ts` is dead unless the provider's code under `apps/sim/providers/{provider}/` consumes it (see Consumption Matrix below). Setting a flag the provider ignores is a warning, not a critical. +5. **Never auto-fix without printing the diff.** Show the user the proposed diff before applying. Get confirmation. + +## Your Task + +When invoked as `/validate-model [model-id]`: + +1. Read the target entries from `models.ts` +2. Live-fetch the provider's official models, pricing, and capability/reasoning pages + at least one secondary source for pricing +3. Inspect the provider implementation to know which flags are actually consumed +4. Run the checklist below per model +5. Report findings (critical / warning / suggestion / unverified) with every cell linked to its source URL +6. Offer to fix; on confirm, edit `models.ts` in a single pass and re-lint + +If `model-id` is omitted, validate every model in the provider. + +## Step 1: Read entries from `models.ts` + +Capture per model: `id`, full `pricing`, full `capabilities`, `contextWindow`, `releaseDate`, `recommended`, `speedOptimized`, `deprecated`. + +## Step 2: Live-fetch authoritative sources + +Use the canonical provider URL table in `add-model.md` (Step 1) as the single source of truth — fetch the models index, pricing, and reasoning/parameter caveats pages listed there for the target provider. If you update one table, update the other in the same change. + +Secondary cross-check (use at least one): OpenRouter, Artificial Analysis, CloudPrice. + +If a fetch fails (404, timeout, paywall), record the URL attempted and mark dependent fields UNVERIFIED. + +## Step 3: Build the consumption map for this provider + +Re-grep before trusting the snapshot below: + +```bash +rg "reasoningEffort|reasoning_effort" apps/sim/providers// +rg "verbosity" apps/sim/providers// +rg "request\.thinking|thinking:" apps/sim/providers// +rg "supportsNativeStructuredOutputs|nativeStructuredOutputs" apps/sim/providers// +``` + +Snapshot (verify before relying): + +| Capability | Consumed by | +|---|---| +| `reasoningEffort` | `openai/core.ts`, `azure-openai`, `anthropic/core.ts` (mapped via thinking), `gemini/core.ts` | +| `verbosity` | `openai/core.ts`, `azure-openai/index.ts` | +| `thinking` | `anthropic/core.ts`, `gemini/core.ts` | +| `nativeStructuredOutputs` | `anthropic/core.ts`, `fireworks/index.ts`, `openrouter/index.ts` | +| `computerUse` | `anthropic/core.ts` | +| `temperature` | All providers (passthrough) | + +A flag set in `models.ts` but not in the consumption list for this provider = **warning: dead flag**. + +## Step 4: Run the checklist + +For each model, evaluate every row. Statuses: ✓ matches docs, ✗ disagrees, ⚠️ single-source, ❓ UNVERIFIED (could not fetch). + +### Identity +- [ ] `id` exactly matches provider's API model identifier (case, dots, dashes, prefix for resellers) +- [ ] `releaseDate` matches launch announcement +- [ ] `deprecated: true` set if provider has announced retirement (or removed from active list) + +### Pricing (per 1M tokens, USD) +- [ ] `pricing.input` matches provider pricing page +- [ ] `pricing.output` matches provider pricing page +- [ ] `pricing.cachedInput` matches provider's documented cached/prompt-cache rate (or is correctly omitted if no caching offered) +- [ ] `pricing.updatedAt` is recent — warn if older than 60 days + +### Context & output limits +- [ ] `contextWindow` matches docs (in tokens) +- [ ] `capabilities.maxOutputTokens` matches documented output cap (or is correctly omitted if "no output limit") + +### Capabilities (each must be DOCUMENTED-AS-SUPPORTED **and** CONSUMED-BY-PROVIDER-CODE) +- [ ] `temperature` — provider accepts it for this model (reasoning-always-on models often reject) +- [ ] `reasoningEffort.values` — list matches docs; **omitted** for always-reasoning models that reject the parameter (e.g., grok-4.3, where xAI docs explicitly state `reasoning_effort` is not supported). Verify per model — some always-reasoning models (e.g., OpenAI's o-series) DO accept `reasoning_effort` and should keep the flag. +- [ ] `verbosity.values` — only on OpenAI gpt-5.x family; values match docs +- [ ] `thinking.levels` + `thinking.default` — only on Anthropic/Gemini; values match docs +- [ ] `nativeStructuredOutputs` — only on anthropic/fireworks/openrouter; provider must document Structured Outputs / JSON-mode for this model +- [ ] `toolUsageControl` — provider supports `tool_choice` semantics +- [ ] `computerUse` — provider implements computer-use loop AND model is a computer-use SKU +- [ ] `deepResearch` — only on actual deep-research SKUs +- [ ] `memory: false` — only when the model genuinely cannot maintain conversation history + +### Flags +- [ ] `recommended: true` — at most one or two per provider; should be current flagship +- [ ] `speedOptimized: true` — only on smallest/fastest tier (nano / flash-lite / haiku class) + +## Step 5: Report (mandatory format) + +For each model, emit a table with one row per checklist item. Every row that claims ✓ must have a URL. + +```markdown +### Validation — + +| Field | Repo | Live docs | Source URL | Status | +|---|---|---|---|---| +| `input` | $1.25/M | $1.25/M | https://docs.x.ai/... | ✓ | +| `cachedInput` | $0.50/M | $0.20/M | https://cloudprice.net/... | ✗ stale (price cut not picked up) | +| `reasoningEffort` | low/medium/high | rejected by API | https://docs.x.ai/.../reasoning | ✗ inert — selecting silently no-ops | +| `contextWindow` | 1,000,000 | 1,000,000 | https://docs.x.ai/... + https://openrouter.ai/... | ✓ (2 sources) | +| `releaseDate` | 2026-04-30 | not found in scraped pages | _attempted: docs.x.ai, x.ai/news_ | ❓ UNVERIFIED | + +**Findings** +- 🔴 critical — `cachedInput` is wrong: docs say $0.20/M, repo has $0.50/M +- 🟡 warning — `reasoningEffort` is set but provider rejects it for this model (xAI docs explicitly: "reasoning_effort is not supported by grok-4.3") +- 🔵 suggestion — `pricing.updatedAt` is 90 days old; refresh +- ❓ unverified — `releaseDate` could not be confirmed from any fetched page; ask user + +**Disagreements between sources** +- _none_ OR _OpenRouter says $X, provider docs say $Y — went with provider docs_ +``` + +End each multi-model run with a summary count: `N models checked · X critical · Y warnings · Z suggestions · W unverified`. + +## Step 6: Offer to fix + +After reporting, ask: *"Want me to fix the critical and warning items? I'll print the diff first."* On yes: + +1. Print the proposed diff (do not apply yet) +2. Get user confirmation +3. Edit `models.ts` in a single pass +4. Run `bun run lint` +5. Re-run only the failed rows of the checklist on the new state + +## Severity definitions + +- 🔴 **critical** — wrong number or wrong identifier that misleads users about cost or breaks API calls. Examples: incorrect pricing, wrong model id, wrong context window, capability the API rejects. +- 🟡 **warning** — dead code or internal inconsistency. Examples: capability flag the provider ignores, multiple `recommended: true` per provider, `pricing.updatedAt` >60 days old, missing `deprecated: true` on retired model. +- 🔵 **suggestion** — style/consistency. Examples: field order, missing `speedOptimized` on a clearly smallest-tier model. +- ❓ **unverified** — could not fetch an authoritative source for this field. Surface it; never silently confirm. + +## Common bugs this skill catches + +- Pricing drift after a provider price cut (very common — providers cut quarterly) +- `reasoningEffort` set on always-reasoning models that reject the parameter (grok-4.3, o3-pro pattern) +- `nativeStructuredOutputs` set on providers that don't consume the flag (dead) +- `thinking` set on non-Anthropic/non-Gemini providers +- `verbosity` set on non-gpt-5.x models +- Wrong context window (e.g., 128k claimed vs 200k actual) +- Stale `pricing.updatedAt` +- Multiple `recommended: true` per provider after a flagship swap +- Missing `deprecated: true` on retired models (e.g., the xAI batch retiring May 15, 2026) + +## What "I cannot verify this" looks like + +If, after fetching the documented sources, a field cannot be confirmed: + +- Mark the row ❓ UNVERIFIED with the URL(s) attempted +- Surface it in the **Findings** section with severity ❓ +- Do NOT mark the validation as passed +- Ask the user for a docs URL or guidance before changing anything + +The skill is allowed to say *"I could not verify the cached input price for grok-4.3 from the official xAI docs in this session — I attempted [URLs] without finding the value. Third-party sources [URL1, URL2] both report $0.20/M. Confirm before I update."* That is correct behavior. Hallucinating a number is not. diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index 68c5a9901de..4359fd261ea 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -44,13 +44,11 @@ jobs: key: ${{ github.repository }}-turbo-cache path: ./.turbo - - name: Restore Next.js build cache - uses: actions/cache@v5 + - name: Mount Next.js build cache (Sticky Disk) + uses: useblacksmith/stickydisk@v1 with: + key: ${{ github.repository }}-nextjs-cache path: ./apps/sim/.next/cache - key: ${{ runner.os }}-nextjs-${{ hashFiles('bun.lock') }} - restore-keys: | - ${{ runner.os }}-nextjs- - name: Install dependencies run: bun install --frozen-lockfile diff --git a/.gitignore b/.gitignore index 2031f178ded..ef2e7025b0c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,9 @@ # bun specific bun-debug.log* +# this repo uses bun.lock; package-lock.json files are accidental +package-lock.json + # testing /coverage /apps/**/coverage diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index dae53828ccb..4092f8c10a7 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -415,6 +415,17 @@ export function MailIcon(props: SVGProps) { ) } +export function EmailBisonIcon(props: SVGProps) { + return ( + + + + ) +} + export function MailServerIcon(props: SVGProps) { return ( ) { ) } +export function SapConcurIcon(props: SVGProps) { + return ( + + + + + + ) +} + export function ServiceNowIcon(props: SVGProps) { return ( diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index a3e73549b07..b515c6ccdd2 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -51,6 +51,7 @@ import { DynamoDBIcon, ElasticsearchIcon, ElevenLabsIcon, + EmailBisonIcon, EnrichSoIcon, EvernoteIcon, ExaAIIcon, @@ -154,6 +155,7 @@ import { RootlyIcon, S3Icon, SalesforceIcon, + SapConcurIcon, SapS4HanaIcon, SESIcon, SearchIcon, @@ -250,6 +252,7 @@ export const blockTypeToIconMap: Record = { dynamodb: DynamoDBIcon, elasticsearch: ElasticsearchIcon, elevenlabs: ElevenLabsIcon, + emailbison: EmailBisonIcon, enrich: EnrichSoIcon, evernote: EvernoteIcon, exa: ExaAIIcon, @@ -370,6 +373,7 @@ export const blockTypeToIconMap: Record = { rootly: RootlyIcon, s3: S3Icon, salesforce: SalesforceIcon, + sap_concur: SapConcurIcon, sap_s4hana: SapS4HanaIcon, search: SearchIcon, secrets_manager: SecretsManagerIcon, @@ -380,6 +384,7 @@ export const blockTypeToIconMap: Record = { ses: SESIcon, sftp: SftpIcon, sharepoint: MicrosoftSharepointIcon, + sharepoint_v2: MicrosoftSharepointIcon, shopify: ShopifyIcon, similarweb: SimilarwebIcon, sixtyfour: SixtyfourIcon, diff --git a/apps/docs/content/docs/en/tools/agiloft.mdx b/apps/docs/content/docs/en/tools/agiloft.mdx index 235300ea259..5032b74663d 100644 --- a/apps/docs/content/docs/en/tools/agiloft.mdx +++ b/apps/docs/content/docs/en/tools/agiloft.mdx @@ -7,7 +7,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card" {/* MANUAL-CONTENT-START:intro */} @@ -137,6 +137,28 @@ Delete a record from an Agiloft table. | `id` | string | ID of the deleted record | | `deleted` | boolean | Whether the record was successfully deleted | +### `agiloft_get_choice_line_id` + +Resolve the internal numeric ID of a choice-list value, for use in EWSelect WHERE clauses against choice fields. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `instanceUrl` | string | Yes | Agiloft instance URL \(e.g., https://mycompany.agiloft.com\) | +| `knowledgeBase` | string | Yes | Knowledge base name | +| `login` | string | Yes | Agiloft username | +| `password` | string | Yes | Agiloft password | +| `table` | string | Yes | Table name \(e.g., "case", "contracts"\) | +| `fieldName` | string | Yes | Choice field name \(e.g., "priority", "status"\) | +| `value` | string | Yes | Choice display value to resolve \(e.g., "High", "Active"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `choiceLineId` | number | Internal numeric line ID of the choice value | + ### `agiloft_lock_record` Lock, unlock, or check the lock status of an Agiloft record. @@ -254,7 +276,7 @@ List saved searches defined for an Agiloft table. | `searches` | array | List of saved searches for the table | | ↳ `name` | string | Saved search name | | ↳ `label` | string | Saved search display label | -| ↳ `id` | string | Saved search database identifier | +| ↳ `id` | number | Saved search database identifier | | ↳ `description` | string | Saved search description | ### `agiloft_search_records` diff --git a/apps/docs/content/docs/en/tools/emailbison.mdx b/apps/docs/content/docs/en/tools/emailbison.mdx new file mode 100644 index 00000000000..5e3b5d66369 --- /dev/null +++ b/apps/docs/content/docs/en/tools/emailbison.mdx @@ -0,0 +1,449 @@ +--- +title: Email Bison +description: Manage Email Bison leads, campaigns, replies, and tags +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +## Usage Instructions + +Integrate Email Bison into workflows. Create and update leads, manage campaigns, attach leads to campaigns, list replies, and organize leads with tags. + + + +## Tools + +### `emailbison_list_leads` + +Retrieves leads from Email Bison with optional search and tag filters. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `search` | string | No | Search term for filtering leads | +| `campaignStatus` | string | No | Lead campaign status filter: in_sequence, sequence_finished, sequence_stopped, never_contacted, or replied | +| `tagIds` | array | No | Tag IDs to include | +| `items` | number | No | No description | +| `excludedTagIds` | array | No | Tag IDs to exclude | +| `items` | number | No | No description | +| `withoutTags` | boolean | No | Only return leads without tags | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `leads` | array | List of leads | +| `campaigns` | array | List of campaigns | +| `replies` | array | List of replies | +| `tags` | array | List of tags | +| `count` | number | Number of returned records | +| `id` | number | Record ID | +| `uuid` | string | Record UUID | +| `name` | string | Campaign or tag name | +| `first_name` | string | Lead first name | +| `last_name` | string | Lead last name | +| `email` | string | Lead email address | +| `status` | string | Record status | +| `success` | boolean | Whether the action succeeded | +| `message` | string | Action message | + +### `emailbison_get_lead` + +Retrieves a lead by Email Bison lead ID or email address. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `leadId` | string | Yes | Lead ID or email address | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `leads` | array | List of leads | +| `campaigns` | array | List of campaigns | +| `replies` | array | List of replies | +| `tags` | array | List of tags | +| `count` | number | Number of returned records | +| `id` | number | Record ID | +| `uuid` | string | Record UUID | +| `name` | string | Campaign or tag name | +| `first_name` | string | Lead first name | +| `last_name` | string | Lead last name | +| `email` | string | Lead email address | +| `status` | string | Record status | +| `success` | boolean | Whether the action succeeded | +| `message` | string | Action message | + +### `emailbison_create_lead` + +Creates a single lead in Email Bison. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `firstName` | string | Yes | Lead first name | +| `lastName` | string | Yes | Lead last name | +| `email` | string | Yes | Lead email address | +| `title` | string | No | Lead job title | +| `company` | string | No | Lead company | +| `notes` | string | No | Additional notes about the lead | +| `customVariables` | array | No | Custom variables to store on the lead | +| `items` | object | No | Custom variable name | +| `properties` | string | No | Custom variable name | +| `name` | string | No | No description | +| `value` | string | No | No description | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `leads` | array | List of leads | +| `campaigns` | array | List of campaigns | +| `replies` | array | List of replies | +| `tags` | array | List of tags | +| `count` | number | Number of returned records | +| `id` | number | Record ID | +| `uuid` | string | Record UUID | +| `name` | string | Campaign or tag name | +| `first_name` | string | Lead first name | +| `last_name` | string | Lead last name | +| `email` | string | Lead email address | +| `status` | string | Record status | +| `success` | boolean | Whether the action succeeded | +| `message` | string | Action message | + +### `emailbison_update_lead` + +Updates an existing Email Bison lead. Fields omitted from a PUT update may be cleared by Email Bison. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `leadId` | string | Yes | Lead ID or email address | +| `firstName` | string | Yes | Lead first name | +| `lastName` | string | Yes | Lead last name | +| `email` | string | Yes | Lead email address | +| `title` | string | No | Lead job title | +| `company` | string | No | Lead company | +| `notes` | string | No | Additional notes about the lead | +| `customVariables` | array | No | Custom variables to store on the lead | +| `items` | object | No | Custom variable name | +| `properties` | string | No | Custom variable name | +| `name` | string | No | No description | +| `value` | string | No | No description | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `leads` | array | List of leads | +| `campaigns` | array | List of campaigns | +| `replies` | array | List of replies | +| `tags` | array | List of tags | +| `count` | number | Number of returned records | +| `id` | number | Record ID | +| `uuid` | string | Record UUID | +| `name` | string | Campaign or tag name | +| `first_name` | string | Lead first name | +| `last_name` | string | Lead last name | +| `email` | string | Lead email address | +| `status` | string | Record status | +| `success` | boolean | Whether the action succeeded | +| `message` | string | Action message | + +### `emailbison_list_campaigns` + +Retrieves Email Bison campaigns. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `leads` | array | List of leads | +| `campaigns` | array | List of campaigns | +| `replies` | array | List of replies | +| `tags` | array | List of tags | +| `count` | number | Number of returned records | +| `id` | number | Record ID | +| `uuid` | string | Record UUID | +| `name` | string | Campaign or tag name | +| `first_name` | string | Lead first name | +| `last_name` | string | Lead last name | +| `email` | string | Lead email address | +| `status` | string | Record status | +| `success` | boolean | Whether the action succeeded | +| `message` | string | Action message | + +### `emailbison_create_campaign` + +Creates a new Email Bison campaign. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `name` | string | Yes | Campaign name | +| `campaignType` | string | No | Campaign type: outbound or reply_followup | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `leads` | array | List of leads | +| `campaigns` | array | List of campaigns | +| `replies` | array | List of replies | +| `tags` | array | List of tags | +| `count` | number | Number of returned records | +| `id` | number | Record ID | +| `uuid` | string | Record UUID | +| `name` | string | Campaign or tag name | +| `first_name` | string | Lead first name | +| `last_name` | string | Lead last name | +| `email` | string | Lead email address | +| `status` | string | Record status | +| `success` | boolean | Whether the action succeeded | +| `message` | string | Action message | + +### `emailbison_update_campaign` + +Updates Email Bison campaign settings. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `campaignId` | number | Yes | Campaign ID | +| `name` | string | No | Campaign name | +| `maxEmailsPerDay` | number | No | Maximum emails per day | +| `maxNewLeadsPerDay` | number | No | Maximum new leads per day | +| `plainText` | boolean | No | Send plain text emails | +| `openTracking` | boolean | No | Enable open tracking | +| `reputationBuilding` | boolean | No | Enable reputation building | +| `canUnsubscribe` | boolean | No | Enable unsubscribe link | +| `includeAutoRepliesInStats` | boolean | No | Include auto replies in campaign stats | +| `sequencePrioritization` | string | No | Sequence prioritization: followups or new_leads | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `leads` | array | List of leads | +| `campaigns` | array | List of campaigns | +| `replies` | array | List of replies | +| `tags` | array | List of tags | +| `count` | number | Number of returned records | +| `id` | number | Record ID | +| `uuid` | string | Record UUID | +| `name` | string | Campaign or tag name | +| `first_name` | string | Lead first name | +| `last_name` | string | Lead last name | +| `email` | string | Lead email address | +| `status` | string | Record status | +| `success` | boolean | Whether the action succeeded | +| `message` | string | Action message | + +### `emailbison_update_campaign_status` + +Pauses, resumes, or archives an Email Bison campaign. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `campaignId` | number | Yes | Campaign ID | +| `action` | string | Yes | Status action: pause, resume, or archive | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `leads` | array | List of leads | +| `campaigns` | array | List of campaigns | +| `replies` | array | List of replies | +| `tags` | array | List of tags | +| `count` | number | Number of returned records | +| `id` | number | Record ID | +| `uuid` | string | Record UUID | +| `name` | string | Campaign or tag name | +| `first_name` | string | Lead first name | +| `last_name` | string | Lead last name | +| `email` | string | Lead email address | +| `status` | string | Record status | +| `success` | boolean | Whether the action succeeded | +| `message` | string | Action message | + +### `emailbison_attach_leads_to_campaign` + +Adds existing Email Bison leads to a campaign. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `campaignId` | number | Yes | Campaign ID | +| `leadIds` | array | Yes | Lead IDs to add to the campaign | +| `items` | number | No | No description | +| `allowParallelSending` | boolean | No | Force add leads already in sequence in other campaigns | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `leads` | array | List of leads | +| `campaigns` | array | List of campaigns | +| `replies` | array | List of replies | +| `tags` | array | List of tags | +| `count` | number | Number of returned records | +| `id` | number | Record ID | +| `uuid` | string | Record UUID | +| `name` | string | Campaign or tag name | +| `first_name` | string | Lead first name | +| `last_name` | string | Lead last name | +| `email` | string | Lead email address | +| `status` | string | Record status | +| `success` | boolean | Whether the action succeeded | +| `message` | string | Action message | + +### `emailbison_list_replies` + +Retrieves Email Bison replies with optional status, folder, campaign, sender, lead, and tag filters. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `search` | string | No | Search term for replies | +| `status` | string | No | Reply status: interested, automated_reply, or not_automated_reply | +| `folder` | string | No | Reply folder: inbox, sent, spam, bounced, or all | +| `read` | boolean | No | Filter by read state | +| `campaignId` | number | No | Campaign ID | +| `senderEmailId` | number | No | Sender email ID | +| `leadId` | number | No | Lead ID | +| `tagIds` | array | No | Tag IDs to filter replies by | +| `items` | number | No | No description | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `leads` | array | List of leads | +| `campaigns` | array | List of campaigns | +| `replies` | array | List of replies | +| `tags` | array | List of tags | +| `count` | number | Number of returned records | +| `id` | number | Record ID | +| `uuid` | string | Record UUID | +| `name` | string | Campaign or tag name | +| `first_name` | string | Lead first name | +| `last_name` | string | Lead last name | +| `email` | string | Lead email address | +| `status` | string | Record status | +| `success` | boolean | Whether the action succeeded | +| `message` | string | Action message | + +### `emailbison_list_tags` + +Retrieves all Email Bison tags for the authenticated workspace. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `leads` | array | List of leads | +| `campaigns` | array | List of campaigns | +| `replies` | array | List of replies | +| `tags` | array | List of tags | +| `count` | number | Number of returned records | +| `id` | number | Record ID | +| `uuid` | string | Record UUID | +| `name` | string | Campaign or tag name | +| `first_name` | string | Lead first name | +| `last_name` | string | Lead last name | +| `email` | string | Lead email address | +| `status` | string | Record status | +| `success` | boolean | Whether the action succeeded | +| `message` | string | Action message | + +### `emailbison_create_tag` + +Creates a new Email Bison tag. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `name` | string | Yes | Tag name | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `leads` | array | List of leads | +| `campaigns` | array | List of campaigns | +| `replies` | array | List of replies | +| `tags` | array | List of tags | +| `count` | number | Number of returned records | +| `id` | number | Record ID | +| `uuid` | string | Record UUID | +| `name` | string | Campaign or tag name | +| `first_name` | string | Lead first name | +| `last_name` | string | Lead last name | +| `email` | string | Lead email address | +| `status` | string | Record status | +| `success` | boolean | Whether the action succeeded | +| `message` | string | Action message | + +### `emailbison_attach_tags_to_leads` + +Attaches Email Bison tags to one or more leads. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `tagIds` | array | Yes | Tag IDs to attach | +| `items` | number | No | No description | +| `leadIds` | array | Yes | Lead IDs to tag | +| `items` | number | No | No description | +| `skipWebhooks` | boolean | No | Skip Email Bison webhooks for this action | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `leads` | array | List of leads | +| `campaigns` | array | List of campaigns | +| `replies` | array | List of replies | +| `tags` | array | List of tags | +| `count` | number | Number of returned records | +| `id` | number | Record ID | +| `uuid` | string | Record UUID | +| `name` | string | Campaign or tag name | +| `first_name` | string | Lead first name | +| `last_name` | string | Lead last name | +| `email` | string | Lead email address | +| `status` | string | Record status | +| `success` | boolean | Whether the action succeeded | +| `message` | string | Action message | + + diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index 1f780cff3d2..af967f765a0 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -46,6 +46,7 @@ "dynamodb", "elasticsearch", "elevenlabs", + "emailbison", "enrich", "evernote", "exa", @@ -150,6 +151,7 @@ "rootly", "s3", "salesforce", + "sap_concur", "sap_s4hana", "search", "secrets_manager", diff --git a/apps/docs/content/docs/en/tools/posthog.mdx b/apps/docs/content/docs/en/tools/posthog.mdx index 61b50e82810..6b471ef60fb 100644 --- a/apps/docs/content/docs/en/tools/posthog.mdx +++ b/apps/docs/content/docs/en/tools/posthog.mdx @@ -87,7 +87,7 @@ List persons (users) in PostHog. Returns user profiles with their properties and | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `personalApiKey` | string | Yes | PostHog Personal API Key \(for authenticated API access\) | +| `apiKey` | string | Yes | PostHog Personal API Key \(for authenticated API access\) | | `region` | string | No | PostHog region: us \(default\) or eu | | `projectId` | string | Yes | PostHog Project ID \(e.g., "12345" or project UUID\) | | `limit` | number | No | Number of persons to return \(default: 100, max: 100\) | @@ -115,7 +115,7 @@ Get detailed information about a specific person in PostHog by their ID or UUID. | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `personalApiKey` | string | Yes | PostHog Personal API Key \(for authenticated API access\) | +| `apiKey` | string | Yes | PostHog Personal API Key \(for authenticated API access\) | | `region` | string | No | PostHog region: us \(default\) or eu | | `projectId` | string | Yes | PostHog Project ID \(e.g., "12345" or project UUID\) | | `personId` | string | Yes | Person ID or UUID to retrieve \(e.g., "01234567-89ab-cdef-0123-456789abcdef"\) | @@ -139,7 +139,7 @@ Delete a person from PostHog. This will remove all associated events and data. U | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `personalApiKey` | string | Yes | PostHog Personal API Key \(for authenticated API access\) | +| `apiKey` | string | Yes | PostHog Personal API Key \(for authenticated API access\) | | `region` | string | No | PostHog region: us \(default\) or eu | | `projectId` | string | Yes | PostHog Project ID \(e.g., "12345" or project UUID\) | | `personId` | string | Yes | Person ID or UUID to delete \(e.g., "01234567-89ab-cdef-0123-456789abcdef"\) | @@ -158,7 +158,7 @@ Execute a HogQL query in PostHog. HogQL is PostHog | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `personalApiKey` | string | Yes | PostHog Personal API Key \(for authenticated API access\) | +| `apiKey` | string | Yes | PostHog Personal API Key \(for authenticated API access\) | | `region` | string | No | PostHog region: us \(default\) or eu | | `projectId` | string | Yes | PostHog Project ID \(e.g., "12345" or project UUID\) | | `query` | string | Yes | HogQL query to execute. Example: \{"kind": "HogQLQuery", "query": "SELECT event, count\(\) FROM events WHERE timestamp > now\(\) - INTERVAL 1 DAY GROUP BY event"\} | diff --git a/apps/docs/content/docs/en/tools/sap_concur.mdx b/apps/docs/content/docs/en/tools/sap_concur.mdx new file mode 100644 index 00000000000..19ce53cb7f5 --- /dev/null +++ b/apps/docs/content/docs/en/tools/sap_concur.mdx @@ -0,0 +1,2761 @@ +--- +title: SAP Concur +description: Manage expense reports, travel requests, cash advances, and more in SAP Concur +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[SAP Concur](https://www.concur.com/) is a leading cloud-based platform for travel, expense, and invoice management. It helps organizations capture spend across every channel — corporate cards, receipts, mileage, and travel bookings — and route it through approval, audit, and reimbursement workflows. + +With SAP Concur, you can: + +- **Manage expense reports end-to-end**: Create reports, add expenses and itemizations, allocate costs, attach receipts, and run them through submit/approve/recall/send-back workflows +- **Capture spend at the source**: Upload receipt images, create quick expenses with images, and pull receipts back for matching and audit +- **Automate travel programs**: List and inspect trips and itineraries, manage travel requests with cash advance support, and read traveler profiles +- **Govern users and reference data**: Provision identities via SCIM v4.1, maintain custom lists for cost centers and projects, and look up locations, exchange rates, and budgets +- **Issue and track cash advances**: Create, retrieve, and issue cash advances tied to travel requests or expense reports + +In Sim, the SAP Concur integration empowers your agents to automate AP and travel workflows across every Concur datacenter. Use tool actions to: + +- **Drive expense automation**: Programmatically build, submit, and route expense reports without users opening the Concur UI +- **Sync identities and reference data**: Keep employees, cost centers, and project codes aligned with your HRIS and ERP systems +- **Process receipts at scale**: Forward receipt images from email or Slack into Concur for OCR, matching, and reimbursement +- **Build approval bots**: Surface pending reports to managers, summarize line items, and approve or send back with a single message + +These capabilities let you eliminate manual data entry, accelerate close cycles, and run your travel and expense program as code — all as part of your workflows. +{/* MANUAL-CONTENT-END */} + + +## Usage Instructions + +Connect SAP Concur via OAuth 2.0. Manage expense reports and line items, allocations, attendees, comments, exceptions, quick expenses, receipts, travel requests and expected expenses, cash advances, itineraries, user identities, custom lists, budgets, exchange rates, and purchase requests across every Concur datacenter. + + + +## Tools + +### `sap_concur_approve_expense_report` + +Approve an expense report as a manager (PATCH /expensereports/v4/reports/\{reportId\}/approve). Required body field: comment. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `reportId` | string | Yes | Expense report ID to approve | +| `body` | json | Yes | Request body — `comment` is required by Concur \(e.g., \{ "comment": "Approved" \}\). If the report contains rejected expenses, `expenseRejectedComment` is also required. Optional fields: `expectedStepCode`, `expectedStepSequence`, `statusId` \(defaults to "A_APPR"\). | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Empty \(204 No Content\) | + +### `sap_concur_associate_attendees` + +Associate attendees with an expense (POST /expensereports/v4/users/\{userId\}/context/\{contextType\}/reports/\{reportId\}/expenses/\{expenseId\}/attendees). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `userId` | string | Yes | Concur user UUID | +| `contextType` | string | Yes | Access context: TRAVELER or PROXY | +| `reportId` | string | Yes | Expense report ID | +| `expenseId` | string | Yes | Expense ID | +| `body` | json | Yes | Attendee associations payload \(e.g., \{ "attendeeAssociations": \[...\] \}\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Concur association response \(201 Created with URI\) | +| ↳ `uri` | string | Resource URI of the attendee associations collection | + +### `sap_concur_create_cash_advance` + +Create a cash advance (POST /cashadvance/v4.1/cashadvances). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `body` | json | Yes | Cash advance payload | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Created cash advance payload | +| ↳ `cashAdvanceId` | string | Unique identifier of the created cash advance | + +### `sap_concur_create_expected_expense` + +Create an expected expense on a travel request (POST /travelrequest/v4/requests/\{requestUuid\}/expenses). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `requestUuid` | string | Yes | Travel request UUID | +| `userId` | string | No | User UUID acting on the request \(required when using a Company JWT, optional otherwise\) | +| `body` | json | Yes | Expected expense payload | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Created expected expense payload | +| ↳ `id` | string | Expected expense identifier | +| ↳ `href` | string | Self-link to the resource | +| ↳ `expenseType` | json | Expense type \{id, name\} | +| ↳ `transactionDate` | string | Transaction date | +| ↳ `transactionAmount` | json | Transaction amount \{value, currencyCode\} | +| ↳ `postedAmount` | json | Posted amount \{value, currencyCode\} | +| ↳ `approvedAmount` | json | Approved amount \{value, currencyCode\} | +| ↳ `remainingAmount` | json | Remaining amount on the expected expense | +| ↳ `businessPurpose` | string | Business purpose of the expense | +| ↳ `location` | json | Location \{id, name, city, countryCode, countrySubDivisionCode, iataCode, locationType\} | +| ↳ `exchangeRate` | json | Exchange rate \{value, operation\} | +| ↳ `allocations` | json | Budget allocations array \(allocationId, allocationAmount, approvedAmount, postedAmount, expenseId, percentEdited, systemAllocation, percentage\) | +| ↳ `tripData` | json | Trip data \{agencyBooked, selfBooked, tripType \(ONE_WAY\|ROUND_TRIP\), legs\[\{id, returnLeg, startDate, startTime, startLocationDetail, startLocation, endLocation, class \{code,value\}, travelExceptionReasonCodes\}\], segmentType \{category, code\}\} | +| ↳ `parentRequest` | json | Parent travel request resource link \{href, id\} | +| ↳ `comments` | json | Comments sub-resource link \{href, id\} | + +### `sap_concur_create_expense_report` + +Create an expense report (POST /expensereports/v4/users/\{userId\}/context/\{contextType\}/reports — supported contexts: TRAVELER, PROXY). Required body fields: name, policyId. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `userId` | string | Yes | Concur user UUID who will own the report | +| `contextType` | string | Yes | Access context: TRAVELER \(creating own report\) or PROXY \(creating on behalf of another user\) | +| `body` | json | Yes | Report payload — `name` and `policyId` are required. Optional fields: businessPurpose, comment, customData, countryCode, countrySubDivisionCode, etc. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Created expense report \(Concur returns 201 with a URI to the new report\) | +| ↳ `uri` | string | URI of the newly created expense report | + +### `sap_concur_create_list_item` + +Create a list item (POST /list/v4/items). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `body` | json | Yes | List item payload. Required: listId, shortCode, value. Optional: parentId or parentCode \(mutually exclusive\). Note: Concur rejects shortCode/value containing hyphens. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Created list item | +| ↳ `id` | string | List item UUID | +| ↳ `code` | string | Long code format for the item | +| ↳ `shortCode` | string | Short code identifier | +| ↳ `value` | string | Display value of the item | +| ↳ `parentId` | string | Parent item UUID \(omitted for first-level items\) | +| ↳ `level` | number | Hierarchy level \(1 for root items\) | +| ↳ `isDeleted` | boolean | Deletion status across all containing lists | +| ↳ `lists` | array | Lists containing this item | +| ↳ `id` | string | List UUID | +| ↳ `hasChildren` | boolean | Whether this item has children in the list | + +### `sap_concur_create_purchase_request` + +Create a purchase request (POST /purchaserequest/v4/purchaserequests). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `body` | json | Yes | Purchase request payload | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Created purchase request payload | +| ↳ `id` | string | Identifier of the created purchase request | +| ↳ `uri` | string | Resource URI for the created purchase request | +| ↳ `errors` | array | Validation or processing errors returned by Concur | +| ↳ `errorCode` | string | Error code | +| ↳ `errorMessage` | string | Error message | +| ↳ `dataPath` | string | Path to the request data which has the error | + +### `sap_concur_create_quick_expense` + +Create a quick expense (POST /quickexpense/v4/users/\{userId\}/context/TRAVELER/quickexpenses). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `userId` | string | Yes | Concur user UUID who owns the quick expense | +| `contextType` | string | Yes | Access context: must be TRAVELER | +| `body` | json | Yes | Quick expense payload \(expenseTypeId, transactionAmount, transactionDate, etc.\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Created quick expense response \(HTTP 201 Created\) | +| ↳ `quickExpenseIdUri` | string | URI of the created quick expense resource | + +### `sap_concur_create_quick_expense_with_image` + +Create a quick expense with an attached image (POST /quickexpense/v4/users/\{userId\}/context/\{contextType\}/quickexpenses/image). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `userId` | string | Yes | Concur user UUID | +| `contextType` | string | Yes | Access context: must be TRAVELER | +| `receipt` | json | Yes | Receipt image \(UserFile\). Allowed: PDF, PNG, JPEG, TIFF \(max 50MB\) | +| `body` | json | Yes | Quick expense payload \(transactionAmount, transactionDate, expenseTypeId, vendor, ...\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Created quick expense response \(HTTP 201 with attached receipt image\) | +| ↳ `quickExpenseIdUri` | string | URI of the created quick expense resource | + +### `sap_concur_create_report_comment` + +Create a comment on a report (POST /expensereports/v4/users/\{userId\}/context/\{contextType\}/reports/\{reportId\}/comments). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `userId` | string | Yes | Concur user UUID | +| `contextType` | string | Yes | Access context: TRAVELER or PROXY | +| `reportId` | string | Yes | Expense report ID | +| `comment` | string | Yes | Comment text to add | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Created comment response \(Concur returns 201 Created with URI\) | +| ↳ `uri` | string | Resource URI of the created comment | + +### `sap_concur_create_travel_request` + +Create a travel request (POST /travelrequest/v4/requests). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `userId` | string | No | Optional Concur user UUID — required when impersonating another user | +| `body` | json | Yes | Travel request payload \(name, purpose, startDate, endDate, requestPolicyId, etc.\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Created travel request payload | +| ↳ `id` | string | Travel request UUID | +| ↳ `href` | string | Resource hyperlink | +| ↳ `requestId` | string | Public-facing request ID \(4-6 alphanumeric characters\) | +| ↳ `name` | string | Request name | +| ↳ `businessPurpose` | string | Business purpose | +| ↳ `comment` | string | Last attached comment | +| ↳ `creationDate` | string | Creation timestamp | +| ↳ `lastModified` | string | Last modification timestamp | +| ↳ `submitDate` | string | Last submission timestamp | +| ↳ `startDate` | string | Trip start date \(ISO 8601\) | +| ↳ `endDate` | string | Trip end date \(ISO 8601\) | +| ↳ `startTime` | string | Trip start time \(HH:mm\) | +| ↳ `endTime` | string | Trip end time \(HH:mm\) | +| ↳ `approved` | boolean | Whether the request is approved | +| ↳ `pendingApproval` | boolean | Pending approval flag | +| ↳ `closed` | boolean | Closed flag | +| ↳ `everSentBack` | boolean | Ever-sent-back flag | +| ↳ `canceledPostApproval` | boolean | Canceled after approval flag | +| ↳ `approvalStatus` | json | Approval status | +| ↳ `code` | string | Status code \(NOT_SUBMITTED, SUBMITTED, APPROVED, CANCELED, SENTBACK\) | +| ↳ `name` | string | Localized status name | +| ↳ `owner` | json | Travel request owner | +| ↳ `id` | string | User UUID | +| ↳ `firstName` | string | Owner first name | +| ↳ `lastName` | string | Owner last name | +| ↳ `approver` | json | Approver assigned to the request | +| ↳ `id` | string | User UUID | +| ↳ `firstName` | string | Approver first name | +| ↳ `lastName` | string | Approver last name | +| ↳ `policy` | json | Resource link to the applicable policy | +| ↳ `id` | string | Policy ID | +| ↳ `href` | string | Policy hyperlink | +| ↳ `type` | json | Request type | +| ↳ `code` | string | Request type code | +| ↳ `label` | string | Request type label | +| ↳ `mainDestination` | json | Main destination of the trip | +| ↳ `city` | string | City | +| ↳ `countryCode` | string | ISO country code | +| ↳ `countrySubDivisionCode` | string | ISO country sub-division code | +| ↳ `name` | string | Destination name | +| ↳ `totalApprovedAmount` | json | Total approved amount | +| ↳ `value` | number | Amount value | +| ↳ `currency` | string | Currency code | +| ↳ `totalPostedAmount` | json | Total posted amount | +| ↳ `value` | number | Amount value | +| ↳ `currency` | string | Currency code | +| ↳ `totalRemainingAmount` | json | Total remaining amount | +| ↳ `value` | number | Amount value | +| ↳ `currency` | string | Currency code | +| ↳ `operations` | array | Available workflow actions | +| ↳ `rel` | string | Operation name | +| ↳ `href` | string | Operation URL | +| ↳ `expenses` | array | Expected expenses attached to the request | +| ↳ `highestExceptionLevel` | string | Highest exception level \(NONE, WARNING, ERROR\) | +| ↳ `travelAgency` | json | Travel agency reference | +| ↳ `id` | string | Agency identifier | +| ↳ `href` | string | Agency URL | +| ↳ `template` | string | Template URL | +| ↳ `custom1` | json | Custom field 1 | +| ↳ `custom2` | json | Custom field 2 | +| ↳ `custom3` | json | Custom field 3 | +| ↳ `custom4` | json | Custom field 4 | + +### `sap_concur_create_user` + +Create a new user identity (POST /profile/identity/v4.1/Users). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `body` | json | Yes | SCIM User payload \(schemas, userName, name, emails, active, etc.\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Created SCIM User payload | + +### `sap_concur_delete_expected_expense` + +Delete an expected expense (DELETE /travelrequest/v4/expenses/\{expenseUuid\}). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `expenseUuid` | string | Yes | Expected expense UUID to delete | +| `userId` | string | No | User UUID acting on the request \(required when using a Company JWT, optional otherwise\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Returns boolean true on 200 OK when the expected expense is deleted. | + +### `sap_concur_delete_expense` + +Delete an expense (DELETE /expensereports/v4/reports/\{reportId\}/expenses/\{expenseId\}). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `reportId` | string | Yes | Expense report ID | +| `expenseId` | string | Yes | Expense ID to delete | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Empty body on success \(HTTP 204 No Content\). Error details when status is non-2xx | + +### `sap_concur_delete_expense_report` + +Delete an expense report (DELETE /expensereports/v4/reports/\{reportId\}). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `reportId` | string | Yes | Expense report ID to delete | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Empty \(204 No Content\) | + +### `sap_concur_delete_list_item` + +Delete a list item (DELETE /list/v4/items/\{itemId\}). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `itemId` | string | Yes | List item UUID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Empty body on success \(HTTP 204 No Content\). Error details when status is non-2xx | + +### `sap_concur_delete_travel_request` + +Delete a travel request (DELETE /travelrequest/v4/requests/\{requestUuid\}). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `requestUuid` | string | Yes | Travel request UUID to delete | +| `userId` | string | No | Optional Concur user UUID — required when impersonating another user | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Concur delete response payload \(boolean true on 200 OK\) | + +### `sap_concur_delete_user` + +Delete a user identity (DELETE /profile/identity/v4.1/Users/\{id\}). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `userUuid` | string | Yes | User UUID to delete | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Deletion response — empty body on HTTP 204 No Content | + +### `sap_concur_get_allocation` + +Get a single allocation (GET /expensereports/v4/users/\{userId\}/context/\{contextType\}/reports/\{reportId\}/allocations/\{allocationId\}). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `userId` | string | Yes | Concur user UUID | +| `contextType` | string | Yes | Access context: TRAVELER or PROXY | +| `reportId` | string | Yes | Expense report ID | +| `allocationId` | string | Yes | Allocation ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Allocation detail payload | +| ↳ `allocationId` | string | Unique allocation identifier | +| ↳ `accountCode` | string | Ledger account code | +| ↳ `overLimitAccountCode` | string | Account code applied to amounts over the per-allocation limit | +| ↳ `percentage` | number | Allocation percentage | +| ↳ `allocationAmount` | json | Allocation amount \(value, currencyCode\) | +| ↳ `value` | number | Amount value | +| ↳ `currencyCode` | string | ISO 4217 currency code | +| ↳ `approvedAmount` | json | Pro-rated approved amount \(value, currencyCode\) | +| ↳ `value` | number | Amount value | +| ↳ `currencyCode` | string | ISO 4217 currency code | +| ↳ `claimedAmount` | json | Requested reimbursement amount \(value, currencyCode\) | +| ↳ `value` | number | Amount value | +| ↳ `currencyCode` | string | ISO 4217 currency code | +| ↳ `customData` | array | Custom field values \(id, value, isValid\) | +| ↳ `expenseId` | string | Associated expense identifier | +| ↳ `isSystemAllocation` | boolean | True when system-managed | +| ↳ `isPercentEdited` | boolean | True when the percentage was manually edited | + +### `sap_concur_get_budget` + +Get a budget item header by ID (GET /budget/v4/budgetItemHeader/\{id\}). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `budgetId` | string | Yes | Budget item header ID \(syncguid\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Budget header detail payload | +| ↳ `id` | string | Budget item header ID | +| ↳ `name` | string | Admin-facing budget name | +| ↳ `description` | string | User-friendly display name | +| ↳ `budgetItemStatusType` | string | Status: OPEN, CLOSED, or REMOVED | +| ↳ `budgetType` | string | Type: PERSONAL_USE, BUDGET, RESTRICTED, or TEAM | +| ↳ `periodType` | string | Period type: YEARLY, QUARTERLY, MONTHLY, or DATE_RANGE | +| ↳ `currencyCode` | string | ISO 4217 currency code | +| ↳ `isTest` | boolean | Test budget flag | +| ↳ `active` | boolean | Display availability flag | +| ↳ `owned` | boolean | Caller ownership flag | +| ↳ `annualBudget` | number | Total annual budget amount | +| ↳ `createdDate` | string | UTC creation timestamp | +| ↳ `lastModifiedDate` | string | UTC modification timestamp | +| ↳ `fiscalYear` | json | Fiscal year reference \(id, name, startDate, endDate, status\) | +| ↳ `budgetAmounts` | json | Aggregate spend amounts \(pendingAmount, spendAmount, unExpensedAmount, availableAmount, adjustedBudgetAmount, consumedPercent, threshold\) | +| ↳ `owner` | json | Owner user \(externalUserCUUID, employeeUuid, email, employeeId, name\) | +| ↳ `budgetManagers` | array | Manager user objects | +| ↳ `budgetApprovers` | array | Approver user objects | +| ↳ `budgetViewers` | array | Viewer user objects | +| ↳ `budgetTeamMembers` | array | Team member entries \(budgetPerson, startDate, endDate, active, status\) | +| ↳ `budgetCategory` | json | Linked category \(id, name, description, statusType\) | +| ↳ `costObjects` | array | Tracking field values \(fieldDefinitionId, code, value, operator\) | +| ↳ `budgetItemDetails` | array | Per-period detail entries \(id, currencyCode, amount, budgetItemDetailStatusType, fiscalPeriod, budgetAmounts\) | +| ↳ `dateRange` | json | Date range for DATE_RANGE budgets \(startDate, endDate\) | + +### `sap_concur_get_cash_advance` + +Get a cash advance (GET /cashadvance/v4.1/cashadvances/\{cashAdvanceId\}). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `cashAdvanceId` | string | Yes | Cash advance ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Cash advance detail payload | +| ↳ `cashAdvanceId` | string | Unique identifier of the cash advance | +| ↳ `name` | string | Cash advance name | +| ↳ `purpose` | string | Purpose for the cash advance | +| ↳ `comment` | string | Comment recorded on the cash advance | +| ↳ `accountCode` | string | Account code linked to the employee | +| ↳ `requestDate` | string | Datetime the cash advance was requested \(UTC, YYYY-MM-DD hh:mm:ss\) | +| ↳ `issuedDate` | string | Datetime the cash advance was issued \(UTC, YYYY-MM-DD hh:mm:ss\) | +| ↳ `lastModifiedDate` | string | Datetime the cash advance was last modified \(UTC, YYYY-MM-DD hh:mm:ss\) | +| ↳ `hasReceipts` | boolean | Whether the cash advance has receipts | +| ↳ `reimbursementCurrency` | string | Reimbursement currency \(3-letter ISO 4217 currency code\) | +| ↳ `amountRequested` | json | Amount requested for the cash advance | +| ↳ `amount` | string | Requested amount value | +| ↳ `currency` | string | 3-letter ISO 4217 currency code | +| ↳ `availableBalance` | json | Unsubmitted balance for the cash advance | +| ↳ `amount` | string | Balance amount | +| ↳ `currency` | string | 3-letter ISO 4217 currency code | +| ↳ `exchangeRate` | json | Exchange rate that applies to the cash advance | +| ↳ `value` | string | Exchange rate value | +| ↳ `operation` | string | Exchange rate operation \(MULTIPLY\) | +| ↳ `approvalStatus` | json | Approval status of the cash advance | +| ↳ `code` | string | Status code | +| ↳ `name` | string | Status display name | +| ↳ `paymentType` | json | Payment type for the cash advance | +| ↳ `paymentCode` | string | Payment type code | +| ↳ `description` | string | Payment method description | + +### `sap_concur_upload_exchange_rates` + +Bulk upload up to 100 custom exchange rates (POST /exchangerate/v4/rates). Body contains a currency_sets array, each with from_crn_code, to_crn_code, start_date (YYYY-MM-DD), and rate. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `body` | json | Yes | Bulk upload body: \{ currency_sets: \[\{ from_crn_code, to_crn_code, start_date: "YYYY-MM-DD", rate \}\] \} \(max 100 entries\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Bulk-upload exchange rate response \(Exchange Rate v4\) | +| ↳ `overallStatus` | string | Overall result status for the bulk upload \(e.g. SUCCESS, FAILURE\) | +| ↳ `message` | string | Top-level result message | +| ↳ `currencySets` | json | Per-row results: array of \{ from_crn_code, to_crn_code, start_date, rate, statusCode, statusMessage \} | + +### `sap_concur_get_expected_expense` + +Get an expected expense (GET /travelrequest/v4/expenses/\{expenseUuid\}). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `expenseUuid` | string | Yes | Expected expense UUID | +| `userId` | string | No | User UUID acting on the request \(optional\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Expected expense payload | +| ↳ `id` | string | Expected expense identifier | +| ↳ `href` | string | Self-link | +| ↳ `expenseType` | json | Expense type \{id, name\} | +| ↳ `transactionDate` | string | Transaction date | +| ↳ `transactionAmount` | json | Transaction amount \{value, currencyCode\} | +| ↳ `postedAmount` | json | Posted amount \{value, currencyCode\} | +| ↳ `approvedAmount` | json | Approved amount \{value, currencyCode\} | +| ↳ `remainingAmount` | json | Remaining amount on the expected expense | +| ↳ `businessPurpose` | string | Business purpose of the expense | +| ↳ `location` | json | Location \{id, name, city, countryCode, countrySubDivisionCode, iataCode, locationType\} | +| ↳ `exchangeRate` | json | Exchange rate \{value, operation\} | +| ↳ `allocations` | json | Budget allocations array | +| ↳ `tripData` | json | Trip data \{agencyBooked, selfBooked, tripType \(ONE_WAY\|ROUND_TRIP\), legs\[\{id, returnLeg, startDate, startTime, startLocationDetail, startLocation, endLocation, class \{code,value\}, travelExceptionReasonCodes\}\], segmentType \{category, code\}\} | +| ↳ `parentRequest` | json | Parent travel request resource link \{href, id\} | +| ↳ `comments` | json | Comments sub-resource link \{href, id\} | + +### `sap_concur_get_expense` + +Get a single expense (GET /expensereports/v4/users/\{userId\}/context/\{contextType\}/reports/\{reportId\}/expenses/\{expenseId\}). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `userId` | string | Yes | Concur user UUID | +| `contextType` | string | Yes | Access context: TRAVELER, MANAGER, or PROXY | +| `reportId` | string | Yes | Expense report ID | +| `expenseId` | string | Yes | Expense ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Expense detail \(ReportExpenseDetail\) payload | +| ↳ `expenseId` | string | Expense identifier | +| ↳ `allocationSetId` | string | Identifier of the associated allocation set | +| ↳ `allocationState` | string | FULLY_ALLOCATED, NOT_ALLOCATED, or PARTIALLY_ALLOCATED | +| ↳ `expenseType` | json | Expense type \{id, name, code, isDeleted\} | +| ↳ `paymentType` | json | Payment type \{id, name, code\} | +| ↳ `expenseSource` | string | Source of the expense \(CASH, CCARD, EBOOKING, etc.\) | +| ↳ `transactionDate` | string | Transaction date \(YYYY-MM-DD\) | +| ↳ `budgetAccrualDate` | string | Budget accrual date | +| ↳ `transactionAmount` | json | Transaction amount \{currencyCode, value\} | +| ↳ `postedAmount` | json | Posted amount in report currency \{currencyCode, value\} | +| ↳ `claimedAmount` | json | Non-personal claimed amount \{currencyCode, value\} | +| ↳ `approvedAmount` | json | Approved amount \{currencyCode, value\} | +| ↳ `approverAdjustedAmount` | json | Total amount adjusted by the approver | +| ↳ `exchangeRate` | json | Exchange rate \{value, operation\} | +| ↳ `vendor` | json | Vendor info \{id, name, description\} | +| ↳ `location` | json | Location \{id, name, city, countryCode, countrySubDivisionCode\} | +| ↳ `businessPurpose` | string | Business purpose | +| ↳ `comment` | string | Free-form comment associated with the expense | +| ↳ `isExpenseBillable` | boolean | Billable flag | +| ↳ `isPersonalExpense` | boolean | Personal-expense flag | +| ↳ `isExpenseRejected` | boolean | Whether the expense was rejected | +| ↳ `isExcludedFromCashAdvanceByUser` | boolean | Whether the user excluded this from cash advance | +| ↳ `isImageRequired` | boolean | Whether a receipt image is required | +| ↳ `isPaperReceiptRequired` | boolean | Whether a paper receipt is required | +| ↳ `isPaperReceiptReceived` | boolean | Whether a paper receipt was received | +| ↳ `isAutoCreated` | boolean | Auto-creation indicator | +| ↳ `hasBlockingExceptions` | boolean | Whether submission-blocking exceptions exist | +| ↳ `hasExceptions` | boolean | Whether any exceptions exist | +| ↳ `hasMissingReceiptDeclaration` | boolean | Affidavit declaration status | +| ↳ `attendeeCount` | number | Number of attendees | +| ↳ `receiptImageId` | string | Identifier of the attached receipt image | +| ↳ `ereceiptImageId` | string | eReceipt image identifier | +| ↳ `receiptType` | json | Receipt \{id, status\} | +| ↳ `imageCertificationStatus` | string | Receipt image processing/certification status | +| ↳ `ticketNumber` | string | Associated travel ticket number | +| ↳ `travel` | json | Travel data \(airline, car rental, hotel, etc.\) | +| ↳ `travelAllowance` | json | Travel allowance association data | +| ↳ `mileage` | json | Mileage details \(odometerStart, odometerEnd, totalDistance, ...\) | +| ↳ `expenseTaxSummary` | json | Aggregated tax data for the expense | +| ↳ `taxRateLocation` | string | Tax rate location: FOREIGN, HOME, or OUT_OF_PROVINCE | +| ↳ `fuelTypeListItem` | json | Fuel type list item \{id, value, isValid\} | +| ↳ `merchantTaxId` | string | Merchant tax identifier | +| ↳ `customData` | json | Array of custom field values \[\{id, value, isValid\}\] | +| ↳ `parentExpenseId` | string | Identifier of the parent expense \(for itemizations\) | +| ↳ `authorizationRequestExpenseId` | string | Linked travel-request expected expense identifier | +| ↳ `jptRouteId` | string | Japan Public Transport route id | +| ↳ `invoiceId` | string | Invoice identifier | +| ↳ `governmentInvoiceId` | string | Government invoice identifier | +| ↳ `lastModifiedDate` | string | Last modified timestamp | +| ↳ `expenseSourceIdentifiers` | json | Source reference identifiers | +| ↳ `links` | json | HATEOAS links for the expense | + +### `sap_concur_get_expense_report` + +Retrieve a single expense report header by id via Expense Report v4 (/expensereports/v4/users/\{userId\}/context/\{contextType\}/reports/\{reportId\}). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `userId` | string | Yes | Concur user UUID who owns the report | +| `contextType` | string | Yes | Access context: TRAVELER \(own report\), MANAGER \(report under approval\), PROCESSOR, or PROXY | +| `reportId` | string | Yes | Expense report ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Concur expense report header \(ReportDetails\) | +| ↳ `reportId` | string | Unique report identifier | +| ↳ `reportNumber` | string | Report number | +| ↳ `reportFormId` | string | Report form ID | +| ↳ `policyId` | string | Policy ID applied to the report | +| ↳ `policy` | string | Policy name | +| ↳ `name` | string | Report name | +| ↳ `currencyCode` | string | ISO currency code | +| ↳ `currency` | string | Currency name | +| ↳ `approvalStatus` | string | Approval status name | +| ↳ `approvalStatusId` | string | Approval status identifier | +| ↳ `paymentStatus` | string | Payment status name | +| ↳ `paymentStatusId` | string | Payment status identifier | +| ↳ `ledger` | string | Ledger name | +| ↳ `ledgerId` | string | Ledger identifier | +| ↳ `userId` | string | Owner user UUID | +| ↳ `reportDate` | string | Report date \(YYYY-MM-DD\) | +| ↳ `creationDate` | string | Creation timestamp \(ISO 8601\) | +| ↳ `submitDate` | string | Submit timestamp \(ISO 8601\) or null | +| ↳ `startDate` | string | Report period start \(YYYY-MM-DD\) | +| ↳ `endDate` | string | Report period end \(YYYY-MM-DD\) | +| ↳ `approvedAmount` | json | Amount approved \{ value, currencyCode \} | +| ↳ `claimedAmount` | json | Amount claimed \{ value, currencyCode \} | +| ↳ `reportTotal` | json | Report total \{ value, currencyCode \} | +| ↳ `amountDueEmployee` | json | Amount due employee | +| ↳ `amountDueCompany` | json | Amount due company | +| ↳ `amountDueCompanyCard` | json | Amount due company card | +| ↳ `amountCompanyPaid` | json | Amount company has paid | +| ↳ `personalAmount` | json | Personal portion of the report | +| ↳ `paymentConfirmedAmount` | json | Confirmed payment amount | +| ↳ `amountNotApproved` | json | Amount not approved | +| ↳ `totalAmountPaidEmployee` | json | Total amount paid to employee | +| ↳ `concurAuditStatus` | string | Concur audit status | +| ↳ `isFinancialIntegrationEnabled` | boolean | Whether financial integration is enabled | +| ↳ `isSubmitted` | boolean | Whether the report has been submitted | +| ↳ `isSentBack` | boolean | Whether the report has been sent back | +| ↳ `isReopened` | boolean | Whether the report was reopened | +| ↳ `isReportEverSentBack` | boolean | Whether the report was ever sent back | +| ↳ `canRecall` | boolean | Whether the report can be recalled | +| ↳ `canAddExpense` | boolean | Whether expenses can be added to the report | +| ↳ `canReopen` | boolean | Whether the report can be reopened | +| ↳ `isReceiptImageRequired` | boolean | Whether receipt images are required | +| ↳ `isReceiptImageAvailable` | boolean | Whether receipt images are available | +| ↳ `isPaperReceiptsReceived` | boolean | Whether paper receipts were received | +| ↳ `isPendingDelegatorReview` | boolean | Whether pending delegator review | +| ↳ `isFundsAndGrantsIntegrationEligible` | boolean | Funds and grants eligibility | +| ↳ `hasReceivedCashAdvanceReturns` | boolean | Whether cash advance returns received | +| ↳ `analyticsGroupId` | string | Analytics group ID | +| ↳ `hierarchyNodeId` | string | Hierarchy node ID | +| ↳ `allocationFormId` | string | Allocation form ID | +| ↳ `countryCode` | string | ISO country code | +| ↳ `countrySubDivisionCode` | string | ISO country subdivision code | +| ↳ `country` | string | Country name | +| ↳ `businessPurpose` | string | Business purpose | +| ↳ `comment` | string | Header-level comment on the report | +| ↳ `reportVersion` | number | Report version number | +| ↳ `reportType` | string | Report type identifier | +| ↳ `cardProgramStatementPeriodId` | string | Card program statement period ID | +| ↳ `defaultFieldAccess` | string | Default field access \(HD/RO/RW\) | +| ↳ `imageStatus` | string | Image status | +| ↳ `receiptContainerId` | string | Receipt container ID | +| ↳ `receiptStatus` | string | Receipt status | +| ↳ `sponsorId` | string | Sponsor ID | +| ↳ `submitterId` | string | Submitter user ID | +| ↳ `taxConfigId` | string | Tax configuration ID | +| ↳ `redirectFund` | json | Redirect fund object \{ amount, creditCardId \} | +| ↳ `customData` | array | Array of custom data \{ id, value, isValid, listItemUrl \} | +| ↳ `employee` | json | Employee object \{ employeeId, employeeUuid \} | +| ↳ `links` | array | HATEOAS links | + +### `sap_concur_get_itemizations` + +Get expense itemizations (GET /expensereports/v4/users/\{userId\}/context/\{contextType\}/reports/\{reportId\}/expenses/\{expenseId\}/itemizations). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `userId` | string | Yes | Concur user UUID | +| `contextType` | string | Yes | Access context: TRAVELER, MANAGER, or PROXY | +| `reportId` | string | Yes | Expense report ID | +| `expenseId` | string | Yes | Expense ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | array | Array of itemizations \(ReportExpenseSummary\[\]\) | +| ↳ `id` | string | Itemization identifier | +| ↳ `expenseId` | string | Itemization expense id | +| ↳ `allocations` | array | Allocations applied to the itemization | +| ↳ `expenseType` | json | Expense type \{id, name, code, isDeleted\} | +| ↳ `transactionDate` | string | Transaction date \(YYYY-MM-DD\) | +| ↳ `transactionAmount` | json | Transaction amount | +| ↳ `postedAmount` | json | Posted amount | +| ↳ `approvedAmount` | json | Approved amount | +| ↳ `claimedAmount` | json | Claimed amount | +| ↳ `approverAdjustedAmount` | json | Approver-adjusted amount | +| ↳ `paymentType` | json | Payment type | +| ↳ `vendor` | json | Vendor info | +| ↳ `location` | json | Location info | +| ↳ `allocationState` | string | Allocation state | +| ↳ `allocationSetId` | string | Allocation set identifier | +| ↳ `attendeeCount` | number | Attendee count | +| ↳ `businessPurpose` | string | Business purpose | +| ↳ `hasBlockingExceptions` | boolean | Has blocking exceptions | +| ↳ `hasExceptions` | boolean | Has exceptions | +| ↳ `isPersonalExpense` | boolean | Personal expense | +| ↳ `links` | array | HATEOAS links | + +### `sap_concur_get_itinerary` + +Get a single trip/itinerary (GET /api/travel/trip/v1.1/\{tripID\}). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `tripId` | string | Yes | Trip ID | +| `useridType` | string | No | User identifier type \(login, xmlsyncid, uuid\) | +| `useridValue` | string | No | User identifier value \(paired with useridType\) | +| `systemFormat` | string | No | Optional system format \(e.g., GDS\) for the response | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Trip detail payload \(Itinerary v1.1\) | +| ↳ `ItinLocator` | string | Concur trip locator \(trip ID\) | +| ↳ `ClientLocator` | string | Client \(booking source\) trip locator | +| ↳ `ItinSourceName` | string | Booking source name | +| ↳ `BookedVia` | string | How the trip was booked \(e.g. ConcurTravel, Direct\) | +| ↳ `TripName` | string | Trip name | +| ↳ `Status` | string | Trip status \(e.g. Confirmed, Cancelled\) | +| ↳ `Description` | string | Trip description | +| ↳ `Comments` | string | Comments attached to the trip | +| ↳ `CancelComments` | string | Cancellation comments \(when applicable\) | +| ↳ `ProjectName` | string | Associated project name | +| ↳ `StartDateUtc` | string | Trip start datetime in UTC | +| ↳ `EndDateUtc` | string | Trip end datetime in UTC | +| ↳ `StartDateLocal` | string | Trip start datetime in local time | +| ↳ `EndDateLocal` | string | Trip end datetime in local time | +| ↳ `DateCreatedUtc` | string | Trip creation timestamp \(UTC\) | +| ↳ `DateModifiedUtc` | string | Trip last-modified timestamp \(UTC\) | +| ↳ `DateBookedLocal` | string | Booking date in local time | +| ↳ `UserLoginId` | string | Login id of the trip owner | +| ↳ `BookedByFirstName` | string | First name of the booker | +| ↳ `BookedByLastName` | string | Last name of the booker | +| ↳ `IsPersonal` | boolean | Whether the trip is flagged personal | +| ↳ `RuleViolations` | array | Travel rule violations attached to the trip | +| ↳ `Bookings` | array | Bookings \(air/hotel/car/rail\) attached to the trip | + +### `sap_concur_get_list` + +Get a single custom list (GET /list/v4/lists/\{listId\}). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `listId` | string | Yes | List ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | List detail payload | +| ↳ `id` | string | Unique identifier \(UUID\) of the list | +| ↳ `value` | string | Name of the list | +| ↳ `levelCount` | number | Number of levels in the list | +| ↳ `searchCriteria` | string | Search attribute \(TEXT or CODE\) | +| ↳ `displayFormat` | string | Display order \(\(CODE\) TEXT or TEXT \(CODE\)\) | +| ↳ `category` | json | List category | +| ↳ `id` | string | Category UUID | +| ↳ `type` | string | Category type | +| ↳ `isReadOnly` | boolean | Whether the list is read-only | +| ↳ `isDeleted` | boolean | Whether the list has been deleted | +| ↳ `managedBy` | string | Identifier of the managing application or service | +| ↳ `externalThreshold` | number | Threshold from where the level starts being external | + +### `sap_concur_get_list_item` + +Get a single list item (GET /list/v4/items/\{itemId\}). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `itemId` | string | Yes | List item ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | List item detail payload | +| ↳ `id` | string | List item UUID | +| ↳ `code` | string | Long code format for the item | +| ↳ `shortCode` | string | Short code identifier | +| ↳ `value` | string | Display value of the item | +| ↳ `parentId` | string | Parent item UUID \(omitted for first-level items\) | +| ↳ `level` | number | Hierarchy level \(1 for root items\) | +| ↳ `isDeleted` | boolean | Deletion status across all containing lists | +| ↳ `lists` | array | Lists containing this item | +| ↳ `id` | string | List UUID | +| ↳ `hasChildren` | boolean | Whether this item has children in the list | + +### `sap_concur_get_purchase_request` + +Get a purchase request by ID (GET /purchaserequest/v4/purchaserequests/\{id\}). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `purchaseRequestId` | string | Yes | Purchase request ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Purchase request detail payload | +| ↳ `purchaseRequestId` | string | Unique identifier of the purchase request | +| ↳ `purchaseRequestNumber` | string | Human-readable purchase request number | +| ↳ `purchaseRequestQueueStatus` | string | Queue status of the purchase request | +| ↳ `purchaseRequestWorkflowStatus` | string | Workflow status of the purchase request | +| ↳ `purchaseOrders` | array | Purchase orders generated from the request | +| ↳ `purchaseOrderNumber` | string | Purchase order number | +| ↳ `purchaseRequestExceptions` | array | Exceptions raised on the purchase request | +| ↳ `eventCode` | string | Event code | +| ↳ `exceptionCode` | string | Exception code | +| ↳ `isCleared` | boolean | Whether the exception has been cleared | +| ↳ `prExceptionId` | string | Identifier of the exception record | +| ↳ `message` | string | Exception message | + +### `sap_concur_get_receipt` + +Get a single receipt by ID (GET /receipts/v4/\{receiptId\}). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `receiptId` | string | Yes | Receipt ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Receipt detail payload | +| ↳ `id` | string | Receipt identifier | +| ↳ `userId` | string | Owning user UUID | +| ↳ `dateTimeReceived` | string | Timestamp when the receipt was received \(ISO 8601\) | +| ↳ `receipt` | json | Parsed receipt JSON object | +| ↳ `image` | string | Receipt image URL or data reference | +| ↳ `validationSchema` | string | Schema used to validate the receipt | +| ↳ `self` | string | URL to this receipt resource | +| ↳ `template` | string | URL template for receipts | + +### `sap_concur_get_receipt_status` + +Get receipt processing status (GET /receipts/v4/status/\{receiptId\}). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `receiptId` | string | Yes | Receipt ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Receipt status payload | +| ↳ `status` | string | Processing status: ACCEPTED, PROCESSING, PROCESSED, or FAILED | +| ↳ `logs` | array | Array of log entries | +| ↳ `logLevel` | string | Log level | +| ↳ `message` | string | Log message | +| ↳ `timestamp` | string | Log timestamp | + +### `sap_concur_get_travel_profile` + +Get a travel profile (GET /api/travelprofile/v2.0/profile). Returns the calling user by default; pass userid_type and userid_value to impersonate. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `useridType` | string | No | Identifier type: login, xmlsyncid, or uuid | +| `useridValue` | string | No | Identifier value \(login id, xml sync id, or UUID\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Travel profile payload. Concur returns XML; downstream may parse it to a best-effort JSON object with the documented top-level sections. | +| ↳ `General` | json | General profile info \(NamePrefix, FirstName, MiddleName, LastName, NameSuffix, JobTitle, CompanyEmployeeID, EmailAddress, RuleClass, TravelConfigID, etc.\) | +| ↳ `Telephones` | json | Telephone numbers \(Telephone\[\] with Type, CountryCode, PhoneNumber, etc.\) | +| ↳ `Addresses` | json | Address records \(Address\[\] with Type, Street, City, StateProvince, etc.\) | +| ↳ `DriversLicenses` | array | Drivers license records | +| ↳ `NationalIDs` | array | National ID records | +| ↳ `EmailAddresses` | json | Email addresses \(EmailAddress\[\] with Type, Address, Contact, Verified\) | +| ↳ `EmergencyContact` | json | Emergency contact \(Name, Relationship, Phones, Address\) | +| ↳ `Air` | json | Air travel preferences \(HomeAirport, Seat, Meal, AirOther, AirMemberships\) | +| ↳ `Rail` | json | Rail preferences \(Seat, Coach, Berth, Other, RailMemberships\) | +| ↳ `Hotel` | json | Hotel preferences \(SmokingCode, RoomType, HotelOther, HotelMemberships, Accessibility flags\) | +| ↳ `Car` | json | Car rental preferences \(CarSmokingCode, CarType, CarMemberships, etc.\) | +| ↳ `CustomFields` | json | Custom-defined fields configured by the company | +| ↳ `RatePreferences` | json | Rate preferences \(e.g. AAA, AARP, government, military rates\) | +| ↳ `DiscountCodes` | json | Discount codes available to the traveler | +| ↳ `HasNoPassport` | boolean | Whether the traveler has no passport on file | +| ↳ `Roles` | json | Role assignments \(TravelManager, Assistant, etc.\) | +| ↳ `Sponsors` | json | Sponsor information for guest travelers | +| ↳ `TSAInfo` | json | TSA SecureFlight info \(Gender, DateOfBirth, NoMiddleName, etc.\) | +| ↳ `Passports` | json | Passport documents \(Passport\[\] with PassportNumber, Country, Expiration\) | +| ↳ `Visas` | json | Visa documents \(Visa\[\] with VisaNationality, VisaNumber, etc.\) | +| ↳ `UnusedTickets` | json | Unused ticket records | +| ↳ `SouthwestUnusedTickets` | json | Southwest-specific unused ticket records | +| ↳ `AdvantageMemberships` | json | Advantage program memberships | +| ↳ `XmlSyncId` | string | XML sync identifier for the user | +| ↳ `LoginId` | string | Concur login id | +| ↳ `ProfileLastModifiedUTC` | string | UTC timestamp the profile was last modified | + +### `sap_concur_get_travel_request` + +Get a single travel request (GET /travelrequest/v4/requests/\{requestUuid\}). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `requestUuid` | string | Yes | Travel request UUID | +| `userId` | string | No | Optional Concur user UUID — required when impersonating another user | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Travel request detail payload | +| ↳ `id` | string | Travel request UUID | +| ↳ `href` | string | Resource hyperlink | +| ↳ `requestId` | string | Public-facing request ID \(4-6 alphanumeric characters\) | +| ↳ `name` | string | Request name | +| ↳ `businessPurpose` | string | Business purpose | +| ↳ `comment` | string | Last attached comment | +| ↳ `creationDate` | string | Creation timestamp | +| ↳ `lastModified` | string | Last modification timestamp | +| ↳ `submitDate` | string | Last submission timestamp | +| ↳ `authorizedDate` | string | Date when approval was completed | +| ↳ `approvalLimitDate` | string | Required approval deadline | +| ↳ `startDate` | string | Trip start date \(ISO 8601\) | +| ↳ `endDate` | string | Trip end date \(ISO 8601\) | +| ↳ `startTime` | string | Trip start time \(HH:mm\) | +| ↳ `endTime` | string | Trip end time \(HH:mm\) | +| ↳ `pnr` | string | Passenger record number | +| ↳ `approved` | boolean | Whether the request is approved | +| ↳ `pendingApproval` | boolean | Pending approval flag | +| ↳ `closed` | boolean | Closed flag | +| ↳ `everSentBack` | boolean | Ever-sent-back flag | +| ↳ `canceledPostApproval` | boolean | Canceled after approval flag | +| ↳ `isParentRequest` | boolean | Parent request flag | +| ↳ `parentRequestId` | string | Parent budget request ID | +| ↳ `allocationFormId` | string | Allocation form identifier | +| ↳ `highestExceptionLevel` | string | Highest exception level \(WARNING, ERROR, NONE\) | +| ↳ `approvalStatus` | json | Approval status | +| ↳ `code` | string | Status code \(NOT_SUBMITTED, SUBMITTED, APPROVED, CANCELED, SENTBACK\) | +| ↳ `name` | string | Localized status name | +| ↳ `owner` | json | Travel request owner | +| ↳ `id` | string | User UUID | +| ↳ `firstName` | string | Owner first name | +| ↳ `lastName` | string | Owner last name | +| ↳ `approver` | json | Approver assigned to the request | +| ↳ `id` | string | User UUID | +| ↳ `firstName` | string | Approver first name | +| ↳ `lastName` | string | Approver last name | +| ↳ `policy` | json | Resource link to the applicable policy | +| ↳ `id` | string | Policy ID | +| ↳ `href` | string | Policy hyperlink | +| ↳ `type` | json | Request type | +| ↳ `code` | string | Request type code | +| ↳ `label` | string | Request type label | +| ↳ `mainDestination` | json | Main destination of the trip | +| ↳ `city` | string | City | +| ↳ `countryCode` | string | ISO country code | +| ↳ `countrySubDivisionCode` | string | ISO country sub-division code | +| ↳ `name` | string | Destination name | +| ↳ `totalApprovedAmount` | json | Total approved amount | +| ↳ `value` | number | Amount value | +| ↳ `currency` | string | Currency code | +| ↳ `totalPostedAmount` | json | Total posted amount | +| ↳ `value` | number | Amount value | +| ↳ `currency` | string | Currency code | +| ↳ `totalRemainingAmount` | json | Total remaining amount | +| ↳ `value` | number | Amount value | +| ↳ `currency` | string | Currency code | +| ↳ `expenses` | array | Resource links to expected expenses | +| ↳ `cashAdvances` | json | Resource link to cash advances | +| ↳ `id` | string | Resource ID | +| ↳ `href` | string | Resource hyperlink | +| ↳ `comments` | json | Resource link to comments | +| ↳ `id` | string | Resource ID | +| ↳ `href` | string | Resource hyperlink | +| ↳ `exceptions` | json | Resource link to exceptions | +| ↳ `id` | string | Resource ID | +| ↳ `href` | string | Resource hyperlink | +| ↳ `travelAgency` | json | Resource link to travel agency | +| ↳ `id` | string | Resource ID | +| ↳ `href` | string | Resource hyperlink | +| ↳ `parentRequest` | json | Resource link to parent request | +| ↳ `id` | string | Resource ID | +| ↳ `href` | string | Resource hyperlink | +| ↳ `eventRequest` | json | Resource link to parent event request | +| ↳ `id` | string | Resource ID | +| ↳ `href` | string | Resource hyperlink | +| ↳ `operations` | array | Available workflow actions | +| ↳ `rel` | string | Operation name | +| ↳ `href` | string | Operation URL | +| ↳ `expensePolicy` | json | Expense policy reference | +| ↳ `id` | string | Policy identifier | +| ↳ `href` | string | Policy URL | +| ↳ `custom1` | json | Custom field 1 | +| ↳ `custom2` | json | Custom field 2 | +| ↳ `custom3` | json | Custom field 3 | +| ↳ `custom4` | json | Custom field 4 | + +### `sap_concur_get_user` + +Get a single user by UUID (GET /profile/identity/v4.1/Users/\{id\}). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `userUuid` | string | Yes | User UUID | +| `attributes` | string | No | Comma-separated SCIM attributes to include in the response | +| `excludedAttributes` | string | No | Comma-separated SCIM attributes to exclude from the response | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | SCIM User identity payload | + +### `sap_concur_issue_cash_advance` + +Issue a cash advance (POST /cashadvance/v4.1/cashadvances/\{cashAdvanceId\}/issue). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `cashAdvanceId` | string | Yes | Cash advance ID to issue | +| `body` | json | No | Optional request body | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Issue cash advance result payload | +| ↳ `issuedDate` | string | Date the cash advance was issued \(YYYY-MM-DD\) | +| ↳ `status` | json | Cash advance status after the issue action | +| ↳ `code` | string | Status code | +| ↳ `name` | string | Status display name | + +### `sap_concur_list_allocations` + +List allocations on an expense (GET /expensereports/v4/users/\{userId\}/context/\{contextType\}/reports/\{reportId\}/expenses/\{expenseId\}/allocations). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `userId` | string | Yes | Concur user UUID | +| `contextType` | string | Yes | Access context: TRAVELER or PROXY | +| `reportId` | string | Yes | Expense report ID | +| `expenseId` | string | Yes | Expense ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Allocations list payload | + +### `sap_concur_list_attendee_associations` + +List attendees associated with an expense (GET /expensereports/v4/users/\{userId\}/context/\{contextType\}/reports/\{reportId\}/expenses/\{expenseId\}/attendees). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `userId` | string | Yes | Concur user UUID | +| `contextType` | string | Yes | Access context: TRAVELER or PROXY | +| `reportId` | string | Yes | Expense report ID | +| `expenseId` | string | Yes | Expense ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Attendees list payload | +| ↳ `noShowAttendeeCount` | number | Number of unnamed/no-show attendees | +| ↳ `expenseAttendeeList` | array | Attendees associated with the expense, including amounts | +| ↳ `attendeeId` | string | Unique identifier of the attendee | +| ↳ `transactionAmount` | json | Expense portion assigned to this attendee | +| ↳ `value` | number | Numeric amount | +| ↳ `currencyCode` | string | ISO 4217 currency code | +| ↳ `approvedAmount` | json | Approved amount in report currency | +| ↳ `value` | number | Numeric amount | +| ↳ `currencyCode` | string | ISO 4217 currency code | +| ↳ `isAmountUserEdited` | boolean | Whether the amount was manually edited | +| ↳ `isTraveling` | boolean | Whether the attendee is traveling \(affects tax calculations\) | +| ↳ `associatedAttendeeCount` | number | Total attendee count; greater than 1 indicates unnamed attendees | +| ↳ `versionNumber` | number | Version number preserving previous attendee state | +| ↳ `customData` | array | Custom field values for the association | +| ↳ `id` | string | Custom field identifier | +| ↳ `value` | string | Custom field value \(max 48 characters\) | +| ↳ `isValid` | boolean | Whether the value passes validation | +| ↳ `listItemUrl` | string | HATEOAS link for list items | + +### `sap_concur_list_budget_categories` + +List budget categories (GET /budget/v4/budgetCategory). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Budget categories collection payload | + +### `sap_concur_list_budgets` + +List budget item headers (GET /budget/v4/budgetItemHeader). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `adminView` | boolean | No | When true, returns all budgets the caller can administer \(default false\) | +| `offset` | number | No | Page offset \(Concur returns up to 50 budget headers per page\) | +| `responseSchema` | string | No | Response schema variant: "COMPACT" returns a smaller payload | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Budget headers collection payload | +| ↳ `offset` | number | Page offset | +| ↳ `limit` | number | Page size | +| ↳ `totalCount` | number | Total result count | + +### `sap_concur_list_exceptions` + +List exceptions on a report (GET /expensereports/v4/users/\{userId\}/context/\{contextType\}/reports/\{reportId\}/exceptions). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `userId` | string | Yes | Concur user UUID | +| `contextType` | string | Yes | Access context: TRAVELER, MANAGER, or PROXY | +| `reportId` | string | Yes | Expense report ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | array | Array of report header exception entries | +| ↳ `exceptionCode` | string | Unique exception code | +| ↳ `exceptionVisibility` | string | Visibility scope: ALL, APPROVER_PROCESSOR, or PROCESSOR | +| ↳ `isBlocking` | boolean | Whether the exception prevents report submission | +| ↳ `message` | string | Human-readable description of the exception | +| ↳ `expenseId` | string | Related expense entry ID | +| ↳ `allocationId` | string | Related allocation ID, if any | +| ↳ `parentExpenseId` | string | Parent expense ID for itemized entries | + +### `sap_concur_list_expected_expenses` + +List expected expenses on a travel request (GET /travelrequest/v4/requests/\{requestUuid\}/expenses). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `requestUuid` | string | Yes | Travel request UUID | +| `userId` | string | No | User UUID acting on the request \(optional\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Array of expected expense objects. Each entry includes id, href, expenseType \{id,name\}, transactionDate, transactionAmount, postedAmount, approvedAmount, remainingAmount, businessPurpose, location, exchangeRate, allocations, tripData, parentRequest \{href, id\}, comments \{href, id\}. | + +### `sap_concur_list_expenses` + +List expenses on a report (GET /expensereports/v4/users/\{userId\}/context/\{contextType\}/reports/\{reportId\}/expenses). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `userId` | string | Yes | Concur user UUID | +| `contextType` | string | Yes | Access context: TRAVELER, MANAGER, or PROXY | +| `reportId` | string | Yes | Expense report ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | array | Array of expense summary entries \(ReportExpenseSummary\[\]\) | +| ↳ `expenseId` | string | Expense identifier | +| ↳ `expenseType` | json | Expense type \{id, name, code, isDeleted\} | +| ↳ `transactionDate` | string | Transaction date \(YYYY-MM-DD\) | +| ↳ `transactionAmount` | json | Transaction amount \{currencyCode, value\} | +| ↳ `postedAmount` | json | Posted amount | +| ↳ `approvedAmount` | json | Approved amount | +| ↳ `claimedAmount` | json | Claimed amount | +| ↳ `approverAdjustedAmount` | json | Approver-adjusted amount | +| ↳ `paymentType` | json | Payment type \{id, name, code\} | +| ↳ `vendor` | json | Vendor info | +| ↳ `location` | json | Location info | +| ↳ `allocationState` | string | Allocation state | +| ↳ `allocationSetId` | string | Allocation set identifier | +| ↳ `attendeeCount` | number | Attendee count | +| ↳ `businessPurpose` | string | Business purpose | +| ↳ `hasBlockingExceptions` | boolean | Has submission-blocking exceptions | +| ↳ `hasExceptions` | boolean | Has exceptions | +| ↳ `hasMissingReceiptDeclaration` | boolean | Has missing-receipt declaration | +| ↳ `isAutoCreated` | boolean | Auto-created | +| ↳ `isPersonalExpense` | boolean | Personal-expense flag | +| ↳ `isImageRequired` | boolean | Receipt image required | +| ↳ `isPaperReceiptRequired` | boolean | Paper receipt required | +| ↳ `imageCertificationStatus` | string | Receipt image certification status | +| ↳ `receiptImageId` | string | Receipt image identifier | +| ↳ `ereceiptImageId` | string | eReceipt image identifier | +| ↳ `ticketNumber` | string | Ticket number | +| ↳ `exchangeRate` | json | Exchange rate | +| ↳ `travelAllowance` | json | Travel allowance | +| ↳ `expenseSourceIdentifiers` | json | Expense source identifiers | +| ↳ `links` | array | HATEOAS links | + +### `sap_concur_list_expense_reports` + +List expense reports (GET /api/v3.0/expense/reports). Returns a v3 envelope with Items and NextPage. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(us, us2, eu, eu2, cn, emea — defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `user` | string | No | Filter by a specific user \(login id or user identifier\). | +| `submitDateBefore` | string | No | Filter to reports submitted on or before this date \(YYYY-MM-DD\) | +| `submitDateAfter` | string | No | Filter to reports submitted on or after this date \(YYYY-MM-DD\) | +| `paidDateBefore` | string | No | Filter to reports paid on or before this date \(YYYY-MM-DD\) | +| `paidDateAfter` | string | No | Filter to reports paid on or after this date \(YYYY-MM-DD\) | +| `modifiedDateBefore` | string | No | Filter to reports last modified on or before this date \(YYYY-MM-DD\) | +| `modifiedDateAfter` | string | No | Filter to reports last modified on or after this date \(YYYY-MM-DD\) | +| `createDateBefore` | string | No | Filter to reports created on or before this date \(YYYY-MM-DD\) | +| `createDateAfter` | string | No | Filter to reports created on or after this date \(YYYY-MM-DD\) | +| `approvalStatusCode` | string | No | Filter by approval status code \(e.g. A_NOTF, A_PEND, A_APPR\) | +| `paymentStatusCode` | string | No | Filter by payment status code | +| `currencyCode` | string | No | Filter by ISO currency code \(e.g. USD, EUR\) | +| `approverLoginID` | string | No | Filter by approver login ID | +| `limit` | number | No | Number of records per page \(default 25, max 100\) | +| `offset` | string | No | Opaque cursor token returned by a prior call \(NextPage\). | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Concur v3 expense reports envelope | +| ↳ `Items` | array | Array of report header objects | +| ↳ `ID` | string | Report ID | +| ↳ `Name` | string | Report name | +| ↳ `OwnerLoginID` | string | Owner login ID | +| ↳ `OwnerName` | string | Owner display name | +| ↳ `Total` | number | Report total | +| ↳ `TotalApprovedAmount` | number | Total approved amount | +| ↳ `TotalClaimedAmount` | number | Total claimed amount | +| ↳ `AmountDueEmployee` | number | Amount due employee | +| ↳ `CurrencyCode` | string | ISO currency code | +| ↳ `ApprovalStatusName` | string | Approval status name | +| ↳ `ApprovalStatusCode` | string | Approval status code | +| ↳ `PaymentStatusName` | string | Payment status name | +| ↳ `PaymentStatusCode` | string | Payment status code | +| ↳ `ApproverLoginID` | string | Approver login ID | +| ↳ `ApproverName` | string | Approver display name | +| ↳ `HasException` | boolean | Whether the report has any exception | +| ↳ `ReceiptsReceived` | boolean | Whether paper receipts were received | +| ↳ `CreateDate` | string | Creation date | +| ↳ `SubmitDate` | string | Submit date | +| ↳ `LastModifiedDate` | string | Last modified date | +| ↳ `PaidDate` | string | Paid date | +| ↳ `URI` | string | Self URI | +| ↳ `NextPage` | string | URI of the next page \(use as offset cursor\) | + +### `sap_concur_list_itineraries` + +List travel trips/itineraries (GET /api/travel/trip/v1.1). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `startDate` | string | No | Filter trips starting on/after this date \(YYYY-MM-DD\) | +| `endDate` | string | No | Filter trips ending on/before this date \(YYYY-MM-DD\) | +| `bookingType` | string | No | Filter by booking type \(air, car, hotel, rail, etc.\) | +| `useridType` | string | No | User identifier type \(login, xmlsyncid, uuid\) | +| `useridValue` | string | No | User identifier value \(paired with useridType\) | +| `itemsPerPage` | number | No | Items per page | +| `page` | number | No | 1-based page number | +| `includeMetadata` | boolean | No | Include paging metadata in the response | +| `includeCanceledTrips` | boolean | No | Include canceled trips in the result set | +| `createdAfterDate` | string | No | Only trips created after this date \(YYYY-MM-DD\) | +| `createdBeforeDate` | string | No | Only trips created before this date \(YYYY-MM-DD\) | +| `lastModifiedDate` | string | No | Only trips modified on/after this date \(YYYY-MM-DD\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Trips list payload \(Itinerary v1.1 ConnectResponse\) | +| ↳ `Metadata` | json | Paging metadata \(when includeMetadata=true\) | +| ↳ `Paging` | json | Pagination details | +| ↳ `TotalPages` | number | Total pages | +| ↳ `TotalItems` | number | Total items | +| ↳ `Page` | number | Current page | +| ↳ `ItemsPerPage` | number | Items per page | +| ↳ `PreviousPageURL` | string | Previous page URL | +| ↳ `NextPageURL` | string | Next page URL | +| ↳ `ItineraryInfoList` | array | List of itinerary summary records | +| ↳ `ItinLocator` | string | Trip locator \(trip ID\) | +| ↳ `ClientLocator` | string | Client trip locator | +| ↳ `ItinSourceName` | string | Booking source name | +| ↳ `BookedVia` | string | Booking channel | +| ↳ `TripName` | string | Trip name | +| ↳ `Status` | string | Trip status | +| ↳ `Description` | string | Trip description | +| ↳ `StartDateUtc` | string | Start \(UTC\) | +| ↳ `EndDateUtc` | string | End \(UTC\) | +| ↳ `StartDateLocal` | string | Start \(local\) | +| ↳ `EndDateLocal` | string | End \(local\) | +| ↳ `DateCreatedUtc` | string | Created \(UTC\) | +| ↳ `DateModifiedUtc` | string | Modified \(UTC\) | +| ↳ `DateBookedLocal` | string | Booked \(local\) | +| ↳ `UserLoginId` | string | Trip owner login id | +| ↳ `BookedByFirstName` | string | Booker first name | +| ↳ `BookedByLastName` | string | Booker last name | +| ↳ `IsPersonal` | boolean | Personal trip flag | + +### `sap_concur_list_lists` + +List custom lists (GET /list/v4/lists). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `page` | number | No | Page number \(1-based; page size is fixed at 100\) | +| `sortBy` | string | No | Sort field: name, levelcount, or listcategory | +| `sortDirection` | string | No | Sort direction: asc or desc | +| `value` | string | No | Filter by list name | +| `categoryType` | string | No | Filter by category type \(mapped to category.type query param\) | +| `isDeleted` | boolean | No | Include deleted lists | +| `levelCount` | number | No | Filter by number of levels | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Paginated lists collection | +| ↳ `content` | array | Lists in the current page | +| ↳ `id` | string | List UUID | +| ↳ `value` | string | Name of the list | +| ↳ `levelCount` | number | Number of levels in the list | +| ↳ `searchCriteria` | string | Search attribute \(TEXT or CODE\) | +| ↳ `displayFormat` | string | Display order \(\(CODE\) TEXT or TEXT \(CODE\)\) | +| ↳ `category` | json | List category | +| ↳ `id` | string | Category UUID | +| ↳ `type` | string | Category type | +| ↳ `isReadOnly` | boolean | Whether the list is read-only | +| ↳ `isDeleted` | boolean | Whether the list has been deleted | +| ↳ `managedBy` | string | Managing application or service identifier | +| ↳ `externalThreshold` | number | Threshold from where the level starts being external | +| ↳ `page` | json | Pagination metadata | +| ↳ `number` | number | Current page number | +| ↳ `size` | number | Items per page | +| ↳ `totalElements` | number | Total item count | +| ↳ `totalPages` | number | Total page count | +| ↳ `links` | array | Navigation links \(next, previous, first, last\) | +| ↳ `rel` | string | Link relation | +| ↳ `href` | string | Link URL | + +### `sap_concur_list_list_items` + +List the top-level items (children) for a custom list (GET /list/v4/lists/\{listId\}/children). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `listId` | string | Yes | List ID | +| `page` | number | No | Page number \(1-based; page size is fixed at 100\) | +| `sortBy` | string | No | Sort field: value or shortCode | +| `sortDirection` | string | No | Sort direction: asc or desc | +| `hasChildren` | boolean | No | Include only items that have children | +| `isDeleted` | boolean | No | Include deleted items | +| `shortCode` | string | No | Filter by short code | +| `value` | string | No | Filter by display value | +| `shortCodeOrValue` | string | No | Filter by short code OR value | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Paginated list items collection | +| ↳ `content` | array | List items in the current page | +| ↳ `id` | string | List item UUID | +| ↳ `code` | string | Long code format for the item | +| ↳ `shortCode` | string | Short code identifier | +| ↳ `value` | string | Display value of the item | +| ↳ `parentId` | string | Parent item UUID \(omitted for first-level items\) | +| ↳ `level` | number | Hierarchy level \(1 for root items\) | +| ↳ `isDeleted` | boolean | Deletion status across all containing lists | +| ↳ `lists` | array | Lists containing this item | +| ↳ `id` | string | List UUID | +| ↳ `hasChildren` | boolean | Whether this item has children in the list | +| ↳ `page` | json | Pagination metadata | +| ↳ `number` | number | Current page number | +| ↳ `size` | number | Items per page | +| ↳ `totalElements` | number | Total item count | +| ↳ `totalPages` | number | Total page count | +| ↳ `links` | array | Navigation links \(next, previous, first, last\) | +| ↳ `rel` | string | Link relation | +| ↳ `href` | string | Link URL | + +### `sap_concur_list_receipts` + +List receipts for a user (GET /receipts/v4/users/\{userId\}). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `userId` | string | Yes | Concur user UUID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | array | Array of e-receipt objects | +| ↳ `id` | string | Receipt id | +| ↳ `userId` | string | Owner user UUID | +| ↳ `dateTimeReceived` | string | Timestamp the receipt was received | +| ↳ `receipt` | json | Structured receipt data | +| ↳ `image` | string | Receipt image URL or reference | +| ↳ `validationSchema` | string | Validation schema URI | +| ↳ `self` | string | Self URL | +| ↳ `template` | string | Template URL | + +### `sap_concur_list_report_comments` + +List comments on a report (GET /expensereports/v4/users/\{userId\}/context/\{contextType\}/reports/\{reportId\}/comments). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `userId` | string | Yes | Concur user UUID | +| `contextType` | string | Yes | Access context: TRAVELER or PROXY | +| `reportId` | string | Yes | Expense report ID | +| `includeAllComments` | boolean | No | Include comments from all expenses in the report \(default false\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | array | Array of report comment entries | +| ↳ `comment` | string | Comment text | +| ↳ `creationDate` | string | Comment creation timestamp \(ISO 8601\) | +| ↳ `expenseId` | string | Related expense entry ID | +| ↳ `isAuditorComment` | boolean | Whether the comment was added by an auditor | +| ↳ `isLatest` | boolean | Whether this is the latest comment | +| ↳ `createdForEmployeeId` | string | Employee ID the comment was created for | +| ↳ `author` | json | Comment author | +| ↳ `employeeId` | string | Employee identifier | +| ↳ `employeeUuid` | string | Employee UUID | +| ↳ `createdForEmployee` | json | Employee the comment was created for | +| ↳ `employeeId` | string | Employee identifier | +| ↳ `employeeUuid` | string | Employee UUID | +| ↳ `stepInstanceId` | string | Workflow step instance identifier | + +### `sap_concur_list_reports_to_approve` + +List expense reports awaiting approval (GET /expensereports/v4/users/\{userId\}/context/MANAGER/reportsToApprove). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `userId` | string | Yes | Manager user UUID | +| `contextType` | string | No | Access context: must be MANAGER \(default\) | +| `sort` | string | No | Report field name to sort by \(e.g., reportDate\) | +| `order` | string | No | Sort direction: asc or desc | +| `includeDelegateApprovals` | boolean | No | Whether to include reports the caller can approve as a delegate | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | array | Array of reports awaiting approval \(ReportToApprove\[\]\) | +| ↳ `reportId` | string | Unique report identifier | +| ↳ `name` | string | Report name | +| ↳ `reportDate` | string | Report date \(YYYY-MM-DD\) | +| ↳ `reportNumber` | string | User-friendly report number | +| ↳ `submitDate` | string | Submission timestamp \(ISO 8601 UTC\) | +| ↳ `approver` | json | Approver employee \{ employeeId, employeeUuid \} | +| ↳ `employee` | json | Report owner employee \{ employeeId, employeeUuid \} | +| ↳ `amountDueEmployee` | json | Amount due employee \{ value, currencyCode \} | +| ↳ `claimedAmount` | json | Total claimed amount \{ value, currencyCode \} | +| ↳ `totalApprovedAmount` | json | Total approved amount \{ value, currencyCode \} | +| ↳ `hasExceptions` | boolean | Whether the report has exceptions | +| ↳ `reportType` | string | Report creation method identifier | +| ↳ `links` | array | HATEOAS links | + +### `sap_concur_get_request_cash_advance` + +Get a single cash advance assigned to a travel request (GET /travelrequest/v4/cashadvances/\{cashAdvanceUuid\}). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `cashAdvanceUuid` | string | Yes | Cash advance UUID \(returned as part of a travel request\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Cash advance detail | +| ↳ `cashAdvanceId` | string | Unique cash advance identifier | +| ↳ `amountRequested` | json | Requested amount | +| ↳ `value` | number | Amount value | +| ↳ `currency` | string | Currency code | +| ↳ `amount` | number | Amount \(alias\) | +| ↳ `approvalStatus` | json | Approval status | +| ↳ `code` | string | Status code | +| ↳ `name` | string | Status name | +| ↳ `requestDate` | string | Request datetime \(ISO 8601\) | +| ↳ `exchangeRate` | json | Exchange rate | +| ↳ `value` | number | Rate value | +| ↳ `operation` | string | Multiply or divide | + +### `sap_concur_list_travel_profiles_summary` + +List travel profile summaries (GET /api/travelprofile/v2.0/summary). LastModifiedDate is required by Concur. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `lastModifiedDate` | string | Yes | Required UTC datetime in YYYY-MM-DDThh:mm:ss format | +| `page` | number | No | 1-based page number | +| `itemsPerPage` | number | No | Items per page \(max 200\) | +| `travelConfigs` | string | No | Comma-separated travel configuration ids | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Travel profile summary list payload \(Concur returns XML mapped to JSON\) | +| ↳ `Metadata` | json | Paging metadata | +| ↳ `Paging` | json | Pagination details | +| ↳ `TotalPages` | number | Total number of pages | +| ↳ `TotalItems` | number | Total number of items | +| ↳ `Page` | number | Current page | +| ↳ `ItemsPerPage` | number | Items per page | +| ↳ `PreviousPageURL` | string | URL to the previous page | +| ↳ `NextPageURL` | string | URL to the next page | +| ↳ `Data` | array | Array of travel profile summaries | +| ↳ `Status` | string | Status \(Active/Inactive\) | +| ↳ `LoginID` | string | Login identifier | +| ↳ `XmlProfileSyncID` | string | XML profile sync identifier | +| ↳ `ProfileLastModifiedUTC` | string | Last modified timestamp \(UTC\) | +| ↳ `RuleClass` | string | Travel rule class assigned to the profile | +| ↳ `TravelConfigID` | string | Travel configuration identifier | +| ↳ `UUID` | string | Profile UUID | +| ↳ `EmployeeID` | string | Employee ID | +| ↳ `CompanyID` | string | Company ID | + +### `sap_concur_list_travel_request_comments` + +List comments on a travel request (GET /travelrequest/v4/requests/\{requestUuid\}/comments). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `requestUuid` | string | Yes | Travel request UUID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | array | Array of comment entries | +| ↳ `author` | json | Comment author | +| ↳ `firstName` | string | Author first name | +| ↳ `lastName` | string | Author last name | +| ↳ `creationDateTime` | string | Comment creation timestamp \(ISO 8601\) | +| ↳ `isLatest` | boolean | Whether this is the latest comment | +| ↳ `value` | string | Comment text | + +### `sap_concur_list_travel_requests` + +List travel requests (GET /travelrequest/v4/requests). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `view` | string | No | View filter \(e.g., ALL, ACTIVE, PENDING, TOAPPROVE\) | +| `limit` | number | No | Max number of results per page | +| `start` | number | No | Page start cursor \(offset\) | +| `userId` | string | No | Filter by Concur user UUID | +| `approvedBefore` | string | No | ISO 8601 date — return requests approved before this date | +| `approvedAfter` | string | No | ISO 8601 date — return requests approved after this date | +| `modifiedBefore` | string | No | ISO 8601 date — return requests modified before this date | +| `modifiedAfter` | string | No | ISO 8601 date — return requests modified after this date | +| `sortField` | string | No | Field to sort by: startDate, approvalStatus, or requestId \(default startDate\) | +| `sortOrder` | string | No | Sort order: ASC or DESC \(default DESC\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Travel requests list payload | +| ↳ `data` | array | Array of travel request summaries | +| ↳ `id` | string | Travel request UUID | +| ↳ `href` | string | Resource hyperlink | +| ↳ `requestId` | string | Public-facing request ID | +| ↳ `name` | string | Request name | +| ↳ `businessPurpose` | string | Business purpose | +| ↳ `comment` | string | Last attached comment | +| ↳ `creationDate` | string | Creation timestamp | +| ↳ `submitDate` | string | Last submission timestamp | +| ↳ `startDate` | string | Trip start date \(ISO 8601\) | +| ↳ `endDate` | string | Trip end date \(ISO 8601\) | +| ↳ `startTime` | string | Trip start time \(HH:mm\) | +| ↳ `approved` | boolean | Whether the request is approved | +| ↳ `pendingApproval` | boolean | Pending approval flag | +| ↳ `closed` | boolean | Closed flag | +| ↳ `everSentBack` | boolean | Ever-sent-back flag | +| ↳ `canceledPostApproval` | boolean | Canceled after approval flag | +| ↳ `approvalStatus` | json | Approval status | +| ↳ `code` | string | Status code \(NOT_SUBMITTED, SUBMITTED, APPROVED, CANCELED, SENTBACK\) | +| ↳ `name` | string | Localized status name | +| ↳ `owner` | json | Travel request owner | +| ↳ `id` | string | User UUID | +| ↳ `firstName` | string | Owner first name | +| ↳ `lastName` | string | Owner last name | +| ↳ `approver` | json | Approver assigned to the request | +| ↳ `id` | string | User UUID | +| ↳ `firstName` | string | Approver first name | +| ↳ `lastName` | string | Approver last name | +| ↳ `type` | json | Request type | +| ↳ `code` | string | Request type code | +| ↳ `label` | string | Request type label | +| ↳ `totalApprovedAmount` | json | Total approved amount | +| ↳ `value` | number | Amount value | +| ↳ `currency` | string | Currency code | +| ↳ `totalPostedAmount` | json | Total posted amount | +| ↳ `value` | number | Amount value | +| ↳ `currency` | string | Currency code | +| ↳ `totalRemainingAmount` | json | Total remaining amount | +| ↳ `value` | number | Amount value | +| ↳ `currency` | string | Currency code | +| ↳ `expenses` | array | Resource links to expected expenses | +| ↳ `operations` | array | Pagination links \(next, prev, first, last\) | +| ↳ `rel` | string | Link relation | +| ↳ `href` | string | Link target | +| ↳ `method` | string | HTTP method | +| ↳ `name` | string | Link name | + +### `sap_concur_list_users` + +List Concur user identities (GET /profile/identity/v4.1/Users). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `count` | number | No | Max number of users to return \(default 100, max 1000\) | +| `cursor` | string | No | SCIM v4.1 pagination cursor returned by a prior call | +| `attributes` | string | No | Comma-separated list of attributes to include in the response | +| `excludedAttributes` | string | No | Comma-separated list of attributes to exclude from the response | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | SCIM ListResponse with Resources array | + +### `sap_concur_move_travel_request` + +Move a travel request through workflow (POST /travelrequest/v4/requests/\{requestUuid\}/\{action\}). Valid actions: submit, recall, cancel, approve, sendback, close, reopen. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `requestUuid` | string | Yes | Travel request UUID | +| `action` | string | Yes | Workflow action: submit, recall, cancel, approve, sendback, close, reopen | +| `userId` | string | No | Optional Concur user UUID — required when impersonating another user | +| `body` | json | No | Optional payload \(e.g., \{ "comment": "..." \}\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Workflow transition response payload | +| ↳ `id` | string | Travel request UUID | +| ↳ `href` | string | Resource hyperlink | +| ↳ `approvalStatus` | json | Approval status after the workflow transition | +| ↳ `code` | string | Status code \(NOT_SUBMITTED, SUBMITTED, APPROVED, CANCELED, SENTBACK\) | +| ↳ `name` | string | Localized status name | +| ↳ `approver` | json | Approver assigned after the transition | +| ↳ `id` | string | User UUID | +| ↳ `firstName` | string | Approver first name | +| ↳ `lastName` | string | Approver last name | +| ↳ `operations` | array | Available follow-up workflow actions | +| ↳ `rel` | string | Link relation | +| ↳ `href` | string | Link target | +| ↳ `method` | string | HTTP method | +| ↳ `name` | string | Link name | + +### `sap_concur_recall_expense_report` + +Recall a submitted expense report (PATCH /expensereports/v4/users/\{userId\}/context/\{contextType\}/reports/\{reportId\}/recall — supported contexts: TRAVELER, PROXY). No request body is required. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `userId` | string | Yes | Concur user UUID who owns the report | +| `contextType` | string | Yes | Access context: TRAVELER or PROXY | +| `reportId` | string | Yes | Expense report ID to recall | +| `body` | json | No | Optional body. Concur docs don't define a payload for this action; pass an empty object if uncertain. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Empty \(204 No Content\) | + +### `sap_concur_remove_all_attendees` + +Remove all attendees from an expense (DELETE /expensereports/v4/users/\{userId\}/context/\{contextType\}/reports/\{reportId\}/expenses/\{expenseId\}/attendees). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `userId` | string | Yes | Concur user UUID | +| `contextType` | string | Yes | Access context: TRAVELER or PROXY | +| `reportId` | string | Yes | Expense report ID | +| `expenseId` | string | Yes | Expense ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Empty response body \(Concur returns 204 No Content\) | + +### `sap_concur_search_locations` + +Search Concur location reference data (GET /localities/v5/locations). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `searchText` | string | No | Free-text query \(city, airport, landmark, etc.\) | +| `locCode` | string | No | IATA / location code | +| `locationNameId` | string | No | Concur internal location name ID \(UUID\) | +| `locationNameKey` | number | No | Concur internal numeric location name key | +| `countryCode` | string | No | 2-letter ISO 3166-1 country code | +| `subdivisionCode` | string | No | ISO 3166-2:2007 country subdivision \(e.g. US-WA\) | +| `adminRegionId` | string | No | Administrative region ID | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Localities v5 search response | +| ↳ `locations` | array | Array of matching Location records | +| ↳ `id` | string | Location ID \(UUID\) | +| ↳ `code` | string | IATA / location code | +| ↳ `legacyKey` | number | Legacy numeric location key | +| ↳ `timeZoneOffset` | string | IANA timezone or UTC offset | +| ↳ `active` | boolean | Whether the location is active | +| ↳ `point` | json | Geographic coordinates | +| ↳ `latitude` | number | Latitude | +| ↳ `longitude` | number | Longitude | +| ↳ `names` | array | Localized location names | +| ↳ `id` | string | Name ID | +| ↳ `key` | number | Numeric name key | +| ↳ `locale` | string | Locale tag | +| ↳ `name` | string | Display name | +| ↳ `administrativeRegion` | json | Administrative region \(e.g., metro area\) | +| ↳ `id` | string | Region ID | +| ↳ `name` | string | Region name | +| ↳ `country` | json | Country reference | +| ↳ `id` | string | Country ID | +| ↳ `code` | string | ISO country code | +| ↳ `name` | string | Country name | +| ↳ `subDivision` | json | Country subdivision \(state/province\) | +| ↳ `id` | string | Subdivision ID | +| ↳ `code` | string | ISO subdivision code | +| ↳ `name` | string | Subdivision name | +| ↳ `links` | array | HATEOAS links | + +### `sap_concur_search_users` + +Search users via SCIM .search endpoint (POST /profile/identity/v4.1/Users/.search). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `body` | json | Yes | SCIM search request payload \(\{ schemas, attributes, filter, count, startIndex \}\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | SCIM search ListResponse | + +### `sap_concur_send_back_expense_report` + +Send back an expense report to the employee (PATCH /expensereports/v4/reports/\{reportId\}/sendBack). Required body field: comment. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `reportId` | string | Yes | Expense report ID to send back | +| `body` | json | Yes | Request body — `comment` is required by Concur \(e.g., \{ "comment": "Missing receipt" \}\). Optional fields: `expectedStepCode`, `expectedStepSequence`. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Empty \(204 No Content\) | + +### `sap_concur_submit_expense_report` + +Submit an expense report into the workflow via Expense Report v4 (PATCH /expensereports/v4/users/\{userId\}/reports/\{reportId\}/submit). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `userId` | string | Yes | Concur user UUID who owns the report | +| `reportId` | string | Yes | Expense report ID to submit | +| `body` | json | No | Optional body. Concur docs don't define a payload for this action; pass an empty object if uncertain. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Empty \(204 No Content\) | + +### `sap_concur_update_allocation` + +Update an allocation (PATCH /expensereports/v4/users/\{userId\}/context/\{contextType\}/reports/\{reportId\}/allocations/\{allocationId\}). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `userId` | string | Yes | Concur user UUID | +| `contextType` | string | Yes | Access context: TRAVELER or PROXY \(write requires expense.report.readwrite\) | +| `reportId` | string | Yes | Expense report ID | +| `allocationId` | string | Yes | Allocation ID to update | +| `body` | json | Yes | Fields to update on the allocation | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Empty body on success \(Concur returns 204 No Content\) | + +### `sap_concur_update_expected_expense` + +Update an expected expense (PUT /travelrequest/v4/expenses/\{expenseUuid\}). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `expenseUuid` | string | Yes | Expected expense UUID to update | +| `userId` | string | No | User UUID acting on the request \(required when using a Company JWT, optional otherwise\) | +| `body` | json | Yes | Fields to update on the expected expense | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Updated expected expense payload | +| ↳ `id` | string | Expected expense identifier | +| ↳ `href` | string | Self-link | +| ↳ `expenseType` | json | Expense type \{id, name\} | +| ↳ `transactionDate` | string | Transaction date | +| ↳ `transactionAmount` | json | Transaction amount \{value, currencyCode\} | +| ↳ `postedAmount` | json | Posted amount \{value, currencyCode\} | +| ↳ `approvedAmount` | json | Approved amount \{value, currencyCode\} | +| ↳ `remainingAmount` | json | Remaining amount on the expected expense | +| ↳ `businessPurpose` | string | Business purpose of the expense | +| ↳ `location` | json | Location \{id, name, city, countryCode, countrySubDivisionCode, iataCode, locationType\} | +| ↳ `exchangeRate` | json | Exchange rate \{value, operation\} | +| ↳ `allocations` | json | Budget allocations array | +| ↳ `tripData` | json | Trip data \{agencyBooked, selfBooked, tripType \(ONE_WAY\|ROUND_TRIP\), legs\[\{id, returnLeg, startDate, startTime, startLocationDetail, startLocation, endLocation, class \{code,value\}, travelExceptionReasonCodes\}\], segmentType \{category, code\}\} | +| ↳ `parentRequest` | json | Parent travel request resource link \{href, id\} | +| ↳ `comments` | json | Comments sub-resource link \{href, id\} | + +### `sap_concur_update_expense` + +Update an expense (PATCH /expensereports/v4/reports/\{reportId\}/expenses/\{expenseId\}). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `reportId` | string | Yes | Expense report ID | +| `expenseId` | string | Yes | Expense ID to update | +| `body` | json | Yes | PATCH body. Allowed fields: businessPurpose \(string, max 64\), customData \(CustomData\[\]\), expenseSource \(required: EA\|MOB\|OTHER\|SE\|TA\|TR\|UI\), isExpenseRejected \(boolean\), isPaperReceiptReceived \(boolean\). | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Empty body on success \(HTTP 204 No Content\). Error details when status is non-2xx | + +### `sap_concur_update_expense_report` + +Update an unsubmitted expense report (PATCH /expensereports/v4/users/\{userId\}/context/\{contextType\}/reports/\{reportId\} — supported contexts: TRAVELER, PROXY). Body fields: businessPurpose, comment, customData, name, etc. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `userId` | string | Yes | Concur user UUID who owns the report | +| `contextType` | string | Yes | Access context: TRAVELER \(own report\) or PROXY \(editing on behalf of another user\) | +| `reportId` | string | Yes | Expense report ID to update | +| `body` | json | Yes | Fields to update on the report | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Empty \(204 No Content\) | + +### `sap_concur_update_list_item` + +Update a list item (PUT /list/v4/items/\{itemId\}). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `itemId` | string | Yes | List item UUID | +| `body` | json | Yes | List item payload. Required: shortCode, value. Other fields in the body are ignored. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Updated list item | +| ↳ `id` | string | List item UUID | +| ↳ `code` | string | Long code format for the item | +| ↳ `shortCode` | string | Short code identifier | +| ↳ `value` | string | Display value of the item | +| ↳ `parentId` | string | Parent item UUID \(omitted for first-level items\) | +| ↳ `level` | number | Hierarchy level \(1 for root items\) | +| ↳ `isDeleted` | boolean | Deletion status across all containing lists | +| ↳ `lists` | array | Lists containing this item | +| ↳ `id` | string | List UUID | +| ↳ `hasChildren` | boolean | Whether this item has children in the list | + +### `sap_concur_update_travel_request` + +Update a travel request (PUT /travelrequest/v4/requests/\{requestUuid\}). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `requestUuid` | string | Yes | Travel request UUID to update | +| `body` | json | Yes | Fields to update on the travel request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Updated travel request payload | +| ↳ `id` | string | Travel request UUID | +| ↳ `href` | string | Resource hyperlink | +| ↳ `requestId` | string | Public-facing request ID \(4-6 alphanumeric characters\) | +| ↳ `name` | string | Request name | +| ↳ `businessPurpose` | string | Business purpose | +| ↳ `comment` | string | Last attached comment | +| ↳ `creationDate` | string | Creation timestamp | +| ↳ `lastModified` | string | Last modification timestamp | +| ↳ `submitDate` | string | Last submission timestamp | +| ↳ `startDate` | string | Trip start date \(ISO 8601\) | +| ↳ `endDate` | string | Trip end date \(ISO 8601\) | +| ↳ `startTime` | string | Trip start time \(HH:mm\) | +| ↳ `endTime` | string | Trip end time \(HH:mm\) | +| ↳ `approved` | boolean | Whether the request is approved | +| ↳ `pendingApproval` | boolean | Pending approval flag | +| ↳ `closed` | boolean | Closed flag | +| ↳ `everSentBack` | boolean | Ever-sent-back flag | +| ↳ `canceledPostApproval` | boolean | Canceled after approval flag | +| ↳ `approvalStatus` | json | Approval status | +| ↳ `code` | string | Status code \(NOT_SUBMITTED, SUBMITTED, APPROVED, CANCELED, SENTBACK\) | +| ↳ `name` | string | Localized status name | +| ↳ `owner` | json | Travel request owner | +| ↳ `id` | string | User UUID | +| ↳ `firstName` | string | Owner first name | +| ↳ `lastName` | string | Owner last name | +| ↳ `approver` | json | Approver assigned to the request | +| ↳ `id` | string | User UUID | +| ↳ `firstName` | string | Approver first name | +| ↳ `lastName` | string | Approver last name | +| ↳ `policy` | json | Resource link to the applicable policy | +| ↳ `id` | string | Policy ID | +| ↳ `href` | string | Policy hyperlink | +| ↳ `type` | json | Request type | +| ↳ `code` | string | Request type code | +| ↳ `label` | string | Request type label | +| ↳ `mainDestination` | json | Main destination of the trip | +| ↳ `city` | string | City | +| ↳ `countryCode` | string | ISO country code | +| ↳ `countrySubDivisionCode` | string | ISO country sub-division code | +| ↳ `name` | string | Destination name | +| ↳ `totalApprovedAmount` | json | Total approved amount | +| ↳ `value` | number | Amount value | +| ↳ `currency` | string | Currency code | +| ↳ `totalPostedAmount` | json | Total posted amount | +| ↳ `value` | number | Amount value | +| ↳ `currency` | string | Currency code | +| ↳ `totalRemainingAmount` | json | Total remaining amount | +| ↳ `value` | number | Amount value | +| ↳ `currency` | string | Currency code | +| ↳ `operations` | array | Available workflow actions | + +### `sap_concur_update_user` + +Patch a user identity (PATCH /profile/identity/v4.1/Users/\{id\}). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `userUuid` | string | Yes | User UUID to update | +| `body` | json | Yes | SCIM PATCH operations payload \(\{ schemas, Operations: \[...\] \}\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Updated SCIM User payload | + +### `sap_concur_upload_receipt_image` + +Upload an image-only receipt (POST /receipts/v4/users/\{userId\}/image-only-receipts). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `datacenter` | string | No | Concur datacenter base URL \(defaults to us.api.concursolutions.com\) | +| `grantType` | string | No | OAuth grant type: client_credentials \(default\) or password | +| `clientId` | string | Yes | Concur OAuth client ID | +| `clientSecret` | string | Yes | Concur OAuth client secret | +| `username` | string | No | Username \(only for password grant\) | +| `password` | string | No | Password \(only for password grant\) | +| `companyUuid` | string | No | Company UUID for multi-company access tokens | +| `userId` | string | Yes | Concur user UUID who owns the receipt | +| `receipt` | json | Yes | Receipt image file \(UserFile reference\). Supported formats: PDF, PNG, JPEG, GIF, TIFF | +| `forwardId` | string | No | Optional client-supplied dedup id \(max 40 chars\). Sent as the concur-forwardid header. | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | number | HTTP status code returned by Concur | +| `data` | json | Image-only receipt upload response \(HTTP 202 Accepted; Location and Link response headers exposed in body\) | +| ↳ `location` | string | Location header URL for the new receipt image \(e.g. /receipts/v4/images/\{receiptId\}\) | +| ↳ `link` | string | Link header URL pointing to /receipts/v4/status/\{receiptId\} | + + diff --git a/apps/docs/content/docs/en/tools/sap_s4hana.mdx b/apps/docs/content/docs/en/tools/sap_s4hana.mdx index 0c8aaf5c745..bc9bb49302a 100644 --- a/apps/docs/content/docs/en/tools/sap_s4hana.mdx +++ b/apps/docs/content/docs/en/tools/sap_s4hana.mdx @@ -62,10 +62,10 @@ List business partners from SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_BusinessP | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -84,7 +84,22 @@ List business partners from SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_BusinessP | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP | -| `data` | json | Array of A_BusinessPartner entities | +| `data` | json | OData v2 envelope `\{ d: \{ results: \[...\], __count?, __next? \} \}`. Properties listed below describe each element of `data.d.results`. | +| ↳ `BusinessPartner` | string | Business partner key \(up to 10 chars\) | +| ↳ `BusinessPartnerFullName` | string | Full name \(concatenated first/last or organization name\) | +| ↳ `BusinessPartnerCategory` | string | "1" Person, "2" Organization, "3" Group | +| ↳ `BusinessPartnerGrouping` | string | Grouping / number range \(tenant-configured\) | +| ↳ `BusinessPartnerType` | string | Business partner type \(tenant-configured\) | +| ↳ `BusinessPartnerUUID` | string | GUID identifier for the business partner | +| ↳ `BusinessPartnerIsBlocked` | boolean | Whether the business partner is centrally blocked | +| ↳ `FirstName` | string | First name \(Person\) | +| ↳ `LastName` | string | Last name \(Person\) | +| ↳ `OrganizationBPName1` | string | Organization name line 1 | +| ↳ `SearchTerm1` | string | Search term 1 | +| ↳ `CreationDate` | string | Date the partner was created \(OData /Date\(...\)/ literal\) | +| ↳ `CreatedByUser` | string | User who created the business partner | +| ↳ `LastChangeDate` | string | Date of last change \(OData /Date\(...\)/ literal\) | +| ↳ `LastChangedByUser` | string | User who last changed the business partner | ### `sap_s4hana_get_business_partner` @@ -94,10 +109,10 @@ Retrieve a single business partner by BusinessPartner key from SAP S/4HANA Cloud | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -113,7 +128,24 @@ Retrieve a single business partner by BusinessPartner key from SAP S/4HANA Cloud | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP | -| `data` | json | A_BusinessPartner entity | +| `data` | json | A_BusinessPartner entity \(under d in OData v2\) | +| ↳ `BusinessPartner` | string | Business partner key \(up to 10 chars\) | +| ↳ `BusinessPartnerFullName` | string | Full name \(concatenated first/last or organization name\) | +| ↳ `BusinessPartnerCategory` | string | "1" Person, "2" Organization, "3" Group | +| ↳ `BusinessPartnerGrouping` | string | Grouping / number range \(tenant-configured\) | +| ↳ `BusinessPartnerType` | string | Business partner type \(tenant-configured\) | +| ↳ `BusinessPartnerUUID` | string | GUID identifier for the business partner | +| ↳ `BusinessPartnerIsBlocked` | boolean | Whether the business partner is centrally blocked | +| ↳ `FirstName` | string | First name \(Person\) | +| ↳ `LastName` | string | Last name \(Person\) | +| ↳ `OrganizationBPName1` | string | Organization name line 1 | +| ↳ `CorrespondenceLanguage` | string | Correspondence language \(2-char code, e.g. "EN"\) | +| ↳ `SearchTerm1` | string | Search term 1 | +| ↳ `SearchTerm2` | string | Search term 2 | +| ↳ `CreationDate` | string | Date the partner was created \(OData /Date\(...\)/ literal\) | +| ↳ `CreatedByUser` | string | User who created the business partner | +| ↳ `LastChangeDate` | string | Date of last change \(OData /Date\(...\)/ literal\) | +| ↳ `LastChangedByUser` | string | User who last changed the business partner | ### `sap_s4hana_create_business_partner` @@ -123,10 +155,10 @@ Create a business partner in SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_Business | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -144,21 +176,33 @@ Create a business partner in SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_Business | Parameter | Type | Description | | --------- | ---- | ----------- | -| `status` | number | HTTP status code returned by SAP | -| `data` | json | Created A_BusinessPartner entity | +| `status` | number | HTTP status code returned by SAP \(201 on success\) | +| `data` | json | Created A_BusinessPartner entity \(under d in OData v2\) | +| ↳ `BusinessPartner` | string | Generated business partner key \(up to 10 chars\) | +| ↳ `BusinessPartnerFullName` | string | Full name \(concatenated first/last or organization name\) | +| ↳ `BusinessPartnerCategory` | string | "1" Person, "2" Organization, "3" Group | +| ↳ `BusinessPartnerGrouping` | string | Grouping / number range used to assign the key | +| ↳ `BusinessPartnerType` | string | Business partner type \(tenant-configured\) | +| ↳ `BusinessPartnerUUID` | string | GUID identifier for the business partner | +| ↳ `FirstName` | string | First name \(Person\) | +| ↳ `LastName` | string | Last name \(Person\) | +| ↳ `OrganizationBPName1` | string | Organization name line 1 | +| ↳ `CreationDate` | string | Date the partner was created \(OData /Date\(...\)/ literal\) | +| ↳ `CreatedByUser` | string | User who created the business partner | +| ↳ `LastChangeDate` | string | Date of last change \(OData /Date\(...\)/ literal\) | ### `sap_s4hana_update_business_partner` -Update fields on an A_BusinessPartner entity in SAP S/4HANA Cloud (API_BUSINESS_PARTNER). PATCH only sends the fields you provide; existing values are preserved. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET to avoid lost updates. +Update fields on an A_BusinessPartner entity in SAP S/4HANA Cloud (API_BUSINESS_PARTNER). Uses HTTP MERGE (OData v2 partial update) — only the fields you provide are written; existing values are preserved. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET to avoid lost updates. Deep updates on nested associations (e.g. to_BusinessPartnerAddress) are not supported by SAP (KBA 2833338) — use the dedicated child endpoints. #### Input | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -175,6 +219,15 @@ Update fields on an A_BusinessPartner entity in SAP S/4HANA Cloud (API_BUSINESS_ | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP \(204 on success\) | | `data` | json | Null on 204 success, or updated A_BusinessPartner entity if SAP returns one | +| ↳ `BusinessPartner` | string | Business partner key | +| ↳ `BusinessPartnerFullName` | string | Full name \(concatenated first/last or organization name\) | +| ↳ `BusinessPartnerCategory` | string | "1" Person, "2" Organization, "3" Group | +| ↳ `BusinessPartnerGrouping` | string | Grouping / number range | +| ↳ `FirstName` | string | First name \(Person\) | +| ↳ `LastName` | string | Last name \(Person\) | +| ↳ `OrganizationBPName1` | string | Organization name line 1 | +| ↳ `LastChangeDate` | string | Date of last change \(OData /Date\(...\)/ literal\) | +| ↳ `LastChangedByUser` | string | User who last changed the business partner | ### `sap_s4hana_list_customers` @@ -184,10 +237,10 @@ List customers from SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_Customer) with op | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -206,7 +259,30 @@ List customers from SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_Customer) with op | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP | -| `data` | json | Array of A_Customer entities | +| `data` | json | Array of A_Customer entities, or `\{ results, __count?, __next? \}` when pagination metadata is present \(proxy unwraps the OData v2 `d` envelope\). Properties below describe each customer item. | +| ↳ `Customer` | string | Customer key \(up to 10 characters\) | +| ↳ `CustomerName` | string | Name of customer | +| ↳ `CustomerFullName` | string | Full name of the customer | +| ↳ `CustomerAccountGroup` | string | Customer account group | +| ↳ `CustomerClassification` | string | Customer classification code | +| ↳ `CustomerCorporateGroup` | string | Corporate group code | +| ↳ `AuthorizationGroup` | string | Authorization group | +| ↳ `Supplier` | string | Linked supplier account number | +| ↳ `FiscalAddress` | string | Fiscal address ID | +| ↳ `Industry` | string | Industry key | +| ↳ `NielsenRegion` | string | Nielsen ID | +| ↳ `ResponsibleType` | string | Responsible type | +| ↳ `NFPartnerIsNaturalPerson` | string | Natural person indicator | +| ↳ `InternationalLocationNumber1` | string | International location number 1 | +| ↳ `TaxNumberType` | string | Tax number type | +| ↳ `VATRegistration` | string | VAT registration number | +| ↳ `DeletionIndicator` | boolean | Central deletion flag | +| ↳ `OrderIsBlockedForCustomer` | string | Central order block reason code | +| ↳ `PostingIsBlocked` | boolean | Central posting block flag | +| ↳ `DeliveryIsBlocked` | string | Central delivery block reason code | +| ↳ `BillingIsBlockedForCustomer` | string | Central billing block reason code | +| ↳ `CreationDate` | string | Creation date \(OData v2 epoch\) | +| ↳ `CreatedByUser` | string | User who created the customer | ### `sap_s4hana_get_customer` @@ -216,10 +292,10 @@ Retrieve a single customer by Customer key from SAP S/4HANA Cloud (API_BUSINESS_ | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -235,20 +311,43 @@ Retrieve a single customer by Customer key from SAP S/4HANA Cloud (API_BUSINESS_ | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP | -| `data` | json | A_Customer entity | +| `data` | object | A_Customer entity | +| ↳ `Customer` | string | Customer key \(up to 10 characters\) | +| ↳ `CustomerName` | string | Name of customer | +| ↳ `CustomerFullName` | string | Full name of the customer | +| ↳ `CustomerAccountGroup` | string | Customer account group | +| ↳ `CustomerClassification` | string | Customer classification code | +| ↳ `CustomerCorporateGroup` | string | Corporate group code | +| ↳ `AuthorizationGroup` | string | Authorization group | +| ↳ `Supplier` | string | Linked supplier account number | +| ↳ `FiscalAddress` | string | Fiscal address ID | +| ↳ `Industry` | string | Industry key | +| ↳ `NielsenRegion` | string | Nielsen ID | +| ↳ `ResponsibleType` | string | Responsible type | +| ↳ `NFPartnerIsNaturalPerson` | string | Natural person indicator | +| ↳ `InternationalLocationNumber1` | string | International location number 1 | +| ↳ `TaxNumberType` | string | Tax number type | +| ↳ `VATRegistration` | string | VAT registration number | +| ↳ `DeletionIndicator` | boolean | Central deletion flag | +| ↳ `OrderIsBlockedForCustomer` | string | Central order block reason code | +| ↳ `PostingIsBlocked` | boolean | Central posting block flag | +| ↳ `DeliveryIsBlocked` | string | Central delivery block reason code | +| ↳ `BillingIsBlockedForCustomer` | string | Central billing block reason code | +| ↳ `CreationDate` | string | Creation date \(OData v2 epoch\) | +| ↳ `CreatedByUser` | string | User who created the customer | ### `sap_s4hana_update_customer` -Update fields on an A_Customer entity in SAP S/4HANA Cloud (API_BUSINESS_PARTNER). PATCH only sends the fields you provide; existing values are preserved. A_Customer PATCH is limited to modifiable fields such as OrderIsBlockedForCustomer, DeliveryIsBlock, BillingIsBlockedForCustomer, PostingIsBlocked, and DeletionIndicator. If-Match defaults to a wildcard - for safe concurrent updates pass the ETag from a prior GET to avoid lost updates. +Update fields on an A_Customer entity in SAP S/4HANA Cloud (API_BUSINESS_PARTNER). Uses HTTP MERGE (OData v2 partial update) — only the fields you provide are written; existing values are preserved. A_Customer is limited to modifiable fields such as OrderIsBlockedForCustomer, DeliveryIsBlocked, BillingIsBlockedForCustomer (Edm.String reason codes like #### Input | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -256,7 +355,7 @@ Update fields on an A_Customer entity in SAP S/4HANA Cloud (API_BUSINESS_PARTNER | `username` | string | No | Username for HTTP Basic auth | | `password` | string | No | Password for HTTP Basic auth | | `customer` | string | Yes | Customer key to update \(string, up to 10 characters\) | -| `body` | json | Yes | JSON object with A_Customer fields to update \(e.g., \{"OrderIsBlockedForCustomer":true,"DeletionIndicator":false\}\) | +| `body` | json | Yes | JSON object with A_Customer fields to update \(e.g., \{"OrderIsBlockedForCustomer":"01","DeletionIndicator":false\}\). Block-reason fields are Edm.String codes, not booleans. | | `ifMatch` | string | No | If-Match ETag for optimistic concurrency. Defaults to "*" \(unconditional\). | #### Output @@ -264,7 +363,15 @@ Update fields on an A_Customer entity in SAP S/4HANA Cloud (API_BUSINESS_PARTNER | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP \(204 on success\) | -| `data` | json | Null on 204 success, or updated A_Customer entity if SAP returns one | +| `data` | object | Null on 204 success, or updated A_Customer entity if SAP returns one | +| ↳ `Customer` | string | Customer key \(up to 10 characters\) | +| ↳ `CustomerName` | string | Name of customer | +| ↳ `CustomerAccountGroup` | string | Customer account group | +| ↳ `DeletionIndicator` | boolean | Central deletion flag | +| ↳ `OrderIsBlockedForCustomer` | string | Central order block reason code | +| ↳ `PostingIsBlocked` | boolean | Central posting block flag | +| ↳ `DeliveryIsBlocked` | string | Central delivery block reason code | +| ↳ `BillingIsBlockedForCustomer` | string | Central billing block reason code | ### `sap_s4hana_list_suppliers` @@ -274,10 +381,10 @@ List suppliers from SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_Supplier) with op | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -296,7 +403,47 @@ List suppliers from SAP S/4HANA Cloud (API_BUSINESS_PARTNER, A_Supplier) with op | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP | -| `data` | json | Array of A_Supplier entities | +| `data` | json | OData v2 response envelope; collection at output.data.d.results | +| ↳ `d` | json | OData v2 envelope | +| ↳ `results` | array | A_Supplier entities | +| ↳ `Supplier` | string | Supplier key \(up to 10 characters\) | +| ↳ `AlternativePayeeAccountNumber` | string | Account number of the alternative payee | +| ↳ `AuthorizationGroup` | string | Authorization group | +| ↳ `BusinessPartner` | string | Linked BusinessPartner key | +| ↳ `BR_TaxIsSplit` | boolean | Brazil-specific tax split flag | +| ↳ `CreatedByUser` | string | User who created the supplier | +| ↳ `CreationDate` | string | Creation date \(OData v2 epoch\) | +| ↳ `Customer` | string | Linked customer key \(if any\) | +| ↳ `DeletionIndicator` | boolean | Central deletion flag | +| ↳ `BirthDate` | string | Date of birth \(OData v2 epoch\) | +| ↳ `ConcatenatedInternationalLocNo` | string | Concatenated international location number | +| ↳ `FiscalAddress` | string | Fiscal address number | +| ↳ `Industry` | string | Industry key | +| ↳ `InternationalLocationNumber1` | string | International location number, part 1 | +| ↳ `InternationalLocationNumber2` | string | International location number, part 2 | +| ↳ `InternationalLocationNumber3` | string | International location number, part 3 | +| ↳ `IsNaturalPerson` | boolean | Indicates whether the supplier is a natural person | +| ↳ `PaymentIsBlockedForSupplier` | boolean | Payment block flag | +| ↳ `PostingIsBlocked` | boolean | Posting block flag | +| ↳ `PurchasingIsBlocked` | boolean | Purchasing block flag | +| ↳ `ResponsibleType` | string | Type of business \(Brazil\) | +| ↳ `SupplierAccountGroup` | string | Supplier account group | +| ↳ `SupplierCorporateGroup` | string | Corporate group identifier | +| ↳ `SupplierFullName` | string | Full name of the supplier | +| ↳ `SupplierName` | string | Supplier name | +| ↳ `SupplierProcurementBlock` | string | Procurement block at supplier level | +| ↳ `SuplrProofOfDelivRlvtCode` | string | Proof of delivery relevance code | +| ↳ `SuplrQltyInProcmtCertfnValidTo` | string | Quality certification validity end date \(OData v2 epoch\) | +| ↳ `SuplrQualityManagementSystem` | string | Quality management system of the supplier | +| ↳ `TaxNumber1` | string | Tax number 1 | +| ↳ `TaxNumber2` | string | Tax number 2 | +| ↳ `TaxNumber3` | string | Tax number 3 | +| ↳ `TaxNumber4` | string | Tax number 4 | +| ↳ `TaxNumber5` | string | Tax number 5 | +| ↳ `TaxNumberResponsible` | string | Tax number of responsible party | +| ↳ `TaxNumberType` | string | Tax number type | +| ↳ `VATRegistration` | string | VAT registration number | +| ↳ `__next` | string | OData skiptoken URL for next page | ### `sap_s4hana_get_supplier` @@ -306,10 +453,10 @@ Retrieve a single supplier by Supplier key from SAP S/4HANA Cloud (API_BUSINESS_ | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -325,20 +472,58 @@ Retrieve a single supplier by Supplier key from SAP S/4HANA Cloud (API_BUSINESS_ | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP | -| `data` | json | A_Supplier entity | +| `data` | json | OData v2 response envelope; entity at output.data.d | +| ↳ `d` | json | A_Supplier entity | +| ↳ `Supplier` | string | Supplier key \(up to 10 characters\) | +| ↳ `AlternativePayeeAccountNumber` | string | Account number of the alternative payee | +| ↳ `AuthorizationGroup` | string | Authorization group | +| ↳ `BusinessPartner` | string | Linked BusinessPartner key | +| ↳ `BR_TaxIsSplit` | boolean | Brazil-specific tax split flag | +| ↳ `CreatedByUser` | string | User who created the supplier | +| ↳ `CreationDate` | string | Creation date \(OData v2 epoch\) | +| ↳ `Customer` | string | Linked customer key \(if any\) | +| ↳ `DeletionIndicator` | boolean | Central deletion flag | +| ↳ `BirthDate` | string | Date of birth \(OData v2 epoch\) | +| ↳ `ConcatenatedInternationalLocNo` | string | Concatenated international location number | +| ↳ `FiscalAddress` | string | Fiscal address number | +| ↳ `Industry` | string | Industry key | +| ↳ `InternationalLocationNumber1` | string | International location number, part 1 | +| ↳ `InternationalLocationNumber2` | string | International location number, part 2 | +| ↳ `InternationalLocationNumber3` | string | International location number, part 3 | +| ↳ `IsNaturalPerson` | boolean | Indicates whether the supplier is a natural person | +| ↳ `PaymentIsBlockedForSupplier` | boolean | Payment block flag | +| ↳ `PostingIsBlocked` | boolean | Posting block flag | +| ↳ `PurchasingIsBlocked` | boolean | Purchasing block flag | +| ↳ `ResponsibleType` | string | Type of business \(Brazil\) | +| ↳ `SupplierAccountGroup` | string | Supplier account group | +| ↳ `SupplierCorporateGroup` | string | Corporate group identifier | +| ↳ `SupplierFullName` | string | Full name of the supplier | +| ↳ `SupplierName` | string | Supplier name | +| ↳ `SupplierProcurementBlock` | string | Procurement block at supplier level | +| ↳ `SuplrProofOfDelivRlvtCode` | string | Proof of delivery relevance code | +| ↳ `SuplrQltyInProcmtCertfnValidTo` | string | Quality certification validity end date \(OData v2 epoch\) | +| ↳ `SuplrQualityManagementSystem` | string | Quality management system of the supplier | +| ↳ `TaxNumber1` | string | Tax number 1 | +| ↳ `TaxNumber2` | string | Tax number 2 | +| ↳ `TaxNumber3` | string | Tax number 3 | +| ↳ `TaxNumber4` | string | Tax number 4 | +| ↳ `TaxNumber5` | string | Tax number 5 | +| ↳ `TaxNumberResponsible` | string | Tax number of responsible party | +| ↳ `TaxNumberType` | string | Tax number type | +| ↳ `VATRegistration` | string | VAT registration number | ### `sap_s4hana_update_supplier` -Update fields on an A_Supplier entity in SAP S/4HANA Cloud (API_BUSINESS_PARTNER). PATCH only sends the fields you provide; existing values are preserved. A_Supplier PATCH is limited to modifiable fields such as PostingIsBlocked, PurchasingIsBlocked, PaymentIsBlockedForSupplier, DeletionIndicator, and SupplierAccountGroup. If-Match defaults to a wildcard - for safe concurrent updates pass the ETag from a prior GET to avoid lost updates. +Update fields on an A_Supplier entity in SAP S/4HANA Cloud (API_BUSINESS_PARTNER). Uses HTTP MERGE (OData v2 partial update) — only the fields you provide are written; existing values are preserved. A_Supplier is limited to modifiable fields such as PostingIsBlocked, PurchasingIsBlocked, PaymentIsBlockedForSupplier, DeletionIndicator, and SupplierAccountGroup; company-code/purchasing-org segments must be updated via the `to_SupplierCompany` / `to_SupplierPurchasingOrg` deep-update endpoints. If-Match defaults to a wildcard - for safe concurrent updates pass the ETag from a prior GET to avoid lost updates. #### Input | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -354,7 +539,16 @@ Update fields on an A_Supplier entity in SAP S/4HANA Cloud (API_BUSINESS_PARTNER | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP \(204 on success\) | -| `data` | json | Null on 204 success, or updated A_Supplier entity if SAP returns one | +| `data` | json | Null on 204 success, or OData v2 envelope with updated entity at output.data.d when SAP returns a representation | +| ↳ `d` | json | A_Supplier entity \(when SAP returns a representation\) | +| ↳ `Supplier` | string | Supplier key \(up to 10 characters\) | +| ↳ `SupplierName` | string | Supplier name | +| ↳ `SupplierAccountGroup` | string | Supplier account group | +| ↳ `BusinessPartner` | string | Linked BusinessPartner key | +| ↳ `PaymentIsBlockedForSupplier` | boolean | Payment block flag | +| ↳ `PostingIsBlocked` | boolean | Posting block flag | +| ↳ `PurchasingIsBlocked` | boolean | Purchasing block flag | +| ↳ `DeletionIndicator` | boolean | Central deletion flag | ### `sap_s4hana_list_sales_orders` @@ -364,10 +558,10 @@ List sales orders from SAP S/4HANA Cloud (API_SALES_ORDER_SRV, A_SalesOrder) wit | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -386,7 +580,26 @@ List sales orders from SAP S/4HANA Cloud (API_SALES_ORDER_SRV, A_SalesOrder) wit | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP | -| `data` | json | Array of A_SalesOrder entities | +| `data` | json | OData v2 response envelope; collection at output.data.d.results | +| ↳ `d` | json | OData v2 envelope | +| ↳ `results` | array | A_SalesOrder entities | +| ↳ `SalesOrder` | string | Sales order number | +| ↳ `SalesOrderType` | string | Sales document type \(e.g., OR\) | +| ↳ `SalesOrganization` | string | Sales organization | +| ↳ `DistributionChannel` | string | Distribution channel | +| ↳ `OrganizationDivision` | string | Division | +| ↳ `SoldToParty` | string | Sold-to business partner | +| ↳ `TotalNetAmount` | string | Total net amount | +| ↳ `TransactionCurrency` | string | Document currency | +| ↳ `CreationDate` | string | Creation date \(OData /Date\(ms\)/\) | +| ↳ `SalesOrderDate` | string | Sales order date \(OData /Date\(ms\)/\) | +| ↳ `RequestedDeliveryDate` | string | Requested delivery date \(OData /Date\(ms\)/\) | +| ↳ `LastChangeDate` | string | Last change date \(OData /Date\(ms\)/\) | +| ↳ `PurchaseOrderByCustomer` | string | Customer purchase order reference | +| ↳ `OverallSDProcessStatus` | string | Overall sales document process status | +| ↳ `OverallTotalDeliveryStatus` | string | Overall total delivery status | +| ↳ `OverallSDDocumentRejectionSts` | string | Overall sales document rejection status | +| ↳ `__next` | string | OData skiptoken URL for next page | ### `sap_s4hana_get_sales_order` @@ -396,10 +609,10 @@ Retrieve a single sales order by SalesOrder key from SAP S/4HANA Cloud (API_SALE | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -415,7 +628,27 @@ Retrieve a single sales order by SalesOrder key from SAP S/4HANA Cloud (API_SALE | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP | -| `data` | json | A_SalesOrder entity | +| `data` | json | OData v2 response envelope; entity at output.data.d | +| ↳ `d` | json | A_SalesOrder entity | +| ↳ `SalesOrder` | string | Sales order number | +| ↳ `SalesOrderType` | string | Sales document type | +| ↳ `SalesOrganization` | string | Sales organization | +| ↳ `DistributionChannel` | string | Distribution channel | +| ↳ `OrganizationDivision` | string | Division | +| ↳ `SoldToParty` | string | Sold-to business partner | +| ↳ `PurchaseOrderByCustomer` | string | Customer purchase order reference | +| ↳ `SalesOrderDate` | string | Sales order date \(OData /Date\(ms\)/\) | +| ↳ `RequestedDeliveryDate` | string | Requested delivery date \(OData /Date\(ms\)/\) | +| ↳ `PricingDate` | string | Pricing date \(OData /Date\(ms\)/\) | +| ↳ `LastChangeDate` | string | Last change date \(OData /Date\(ms\)/\) | +| ↳ `LastChangeDateTime` | string | Last change timestamp \(OData /Date\(ms\)/\) | +| ↳ `TotalNetAmount` | string | Total net amount | +| ↳ `TransactionCurrency` | string | Document currency | +| ↳ `CreationDate` | string | Creation date | +| ↳ `OverallSDProcessStatus` | string | Overall sales document process status | +| ↳ `OverallTotalDeliveryStatus` | string | Overall total delivery status | +| ↳ `OverallSDDocumentRejectionSts` | string | Overall sales document rejection status | +| ↳ `to_Item` | json | Sales order items \(when $expand=to_Item\) | ### `sap_s4hana_create_sales_order` @@ -425,10 +658,10 @@ Create a sales order in SAP S/4HANA Cloud (API_SALES_ORDER_SRV, A_SalesOrder) wi | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -447,21 +680,34 @@ Create a sales order in SAP S/4HANA Cloud (API_SALES_ORDER_SRV, A_SalesOrder) wi | Parameter | Type | Description | | --------- | ---- | ----------- | -| `status` | number | HTTP status code returned by SAP | -| `data` | json | Created A_SalesOrder entity \(with deep-inserted items if expanded by SAP\) | +| `status` | number | HTTP status code returned by SAP \(201 on create\) | +| `data` | json | OData v2 response envelope; created entity at output.data.d | +| ↳ `d` | json | Created A_SalesOrder entity | +| ↳ `SalesOrder` | string | Newly assigned sales order number | +| ↳ `SalesOrderType` | string | Sales document type | +| ↳ `SalesOrganization` | string | Sales organization | +| ↳ `DistributionChannel` | string | Distribution channel | +| ↳ `OrganizationDivision` | string | Division | +| ↳ `SoldToParty` | string | Sold-to business partner | +| ↳ `TotalNetAmount` | string | Total net amount | +| ↳ `TransactionCurrency` | string | Document currency | +| ↳ `CreationDate` | string | Creation date | +| ↳ `OverallSDProcessStatus` | string | Overall sales document process status | +| ↳ `OverallTotalDeliveryStatus` | string | Overall total delivery status | +| ↳ `to_Item` | json | Deep-inserted sales order items as returned by SAP | ### `sap_s4hana_update_sales_order` -Update fields on an A_SalesOrder entity in SAP S/4HANA Cloud (API_SALES_ORDER_SRV). PATCH only sends the fields you provide; existing values are preserved. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET to avoid lost updates. +Update fields on an A_SalesOrder header in SAP S/4HANA Cloud (API_SALES_ORDER_SRV). Uses HTTP MERGE (OData v2 partial update) — only the fields you provide are written; existing values are preserved. Header-only — deep updates to to_Item / to_Partner / to_PricingElement navigations are not supported (see SAP KBA 2833338); use A_SalesOrderItem operations for line-level changes. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET to avoid lost updates. #### Input | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -477,7 +723,13 @@ Update fields on an A_SalesOrder entity in SAP S/4HANA Cloud (API_SALES_ORDER_SR | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP \(204 on success\) | -| `data` | json | Null on 204 success, or updated A_SalesOrder entity if SAP returns one | +| `data` | json | Null on 204 success; otherwise OData v2 envelope with the updated entity at output.data.d | +| ↳ `d` | json | Updated A_SalesOrder entity \(when SAP returns one\) | +| ↳ `SalesOrder` | string | Sales order number | +| ↳ `SalesOrderType` | string | Sales document type | +| ↳ `PurchaseOrderByCustomer` | string | Customer purchase order reference | +| ↳ `OverallSDProcessStatus` | string | Overall sales document process status | +| ↳ `OverallTotalDeliveryStatus` | string | Overall total delivery status | ### `sap_s4hana_delete_sales_order` @@ -487,10 +739,10 @@ Delete an A_SalesOrder entity in SAP S/4HANA Cloud (API_SALES_ORDER_SRV). Only o | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -505,7 +757,7 @@ Delete an A_SalesOrder entity in SAP S/4HANA Cloud (API_SALES_ORDER_SRV). Only o | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP \(204 on success\) | -| `data` | json | Null on successful deletion | +| `data` | json | Null on successful deletion \(SAP returns 204 No Content\) | ### `sap_s4hana_list_outbound_deliveries` @@ -515,10 +767,10 @@ List outbound deliveries from SAP S/4HANA Cloud (API_OUTBOUND_DELIVERY_SRV;v=000 | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -537,7 +789,27 @@ List outbound deliveries from SAP S/4HANA Cloud (API_OUTBOUND_DELIVERY_SRV;v=000 | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP | -| `data` | json | Array of A_OutbDeliveryHeader entities | +| `data` | json | OData v2 response envelope; collection at output.data.d.results | +| ↳ `d` | json | OData v2 envelope | +| ↳ `results` | array | A_OutbDeliveryHeader entities | +| ↳ `DeliveryDocument` | string | Outbound delivery number | +| ↳ `DeliveryDocumentType` | string | Delivery document type \(e.g., LF\) | +| ↳ `SDDocumentCategory` | string | SD document category \(e.g., J = outbound delivery\) | +| ↳ `ShippingPoint` | string | Shipping point | +| ↳ `ShippingType` | string | Shipping type | +| ↳ `ShipToParty` | string | Ship-to business partner | +| ↳ `SoldToParty` | string | Sold-to business partner | +| ↳ `DeliveryDate` | string | Delivery date \(Edm.DateTime\) | +| ↳ `ActualGoodsMovementDate` | string | Actual goods issue date \(Edm.DateTime\) | +| ↳ `PlannedGoodsIssueDate` | string | Planned goods issue date \(Edm.DateTime\) | +| ↳ `OverallSDProcessStatus` | string | Overall SD process \(delivery\) status | +| ↳ `OverallGoodsMovementStatus` | string | Overall goods movement status | +| ↳ `TransactionCurrency` | string | Document currency | +| ↳ `DocumentDate` | string | Document date \(Edm.DateTime\) | +| ↳ `CreationDate` | string | Creation date \(Edm.DateTime\) | +| ↳ `LastChangeDate` | string | Last change date \(Edm.DateTime\) | +| ↳ `__next` | string | OData skiptoken URL for next page | +| ↳ `__count` | string | Total count when $inlinecount=allpages is used | ### `sap_s4hana_get_outbound_delivery` @@ -547,10 +819,10 @@ Retrieve a single outbound delivery by DeliveryDocument key from SAP S/4HANA Clo | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -566,7 +838,26 @@ Retrieve a single outbound delivery by DeliveryDocument key from SAP S/4HANA Clo | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP | -| `data` | json | A_OutbDeliveryHeader entity | +| `data` | json | OData v2 response envelope; entity at output.data.d | +| ↳ `d` | json | A_OutbDeliveryHeader entity | +| ↳ `DeliveryDocument` | string | Outbound delivery number | +| ↳ `DeliveryDocumentType` | string | Delivery document type | +| ↳ `SDDocumentCategory` | string | SD document category \(e.g., J = outbound delivery\) | +| ↳ `ShippingPoint` | string | Shipping point | +| ↳ `ShippingType` | string | Shipping type | +| ↳ `ShipToParty` | string | Ship-to business partner | +| ↳ `SoldToParty` | string | Sold-to business partner | +| ↳ `DeliveryDate` | string | Delivery date \(Edm.DateTime\) | +| ↳ `ActualGoodsMovementDate` | string | Actual goods issue date \(Edm.DateTime\) | +| ↳ `PlannedGoodsIssueDate` | string | Planned goods issue date \(Edm.DateTime\) | +| ↳ `OverallSDProcessStatus` | string | Overall SD process \(delivery\) status | +| ↳ `OverallGoodsMovementStatus` | string | Overall goods movement status | +| ↳ `TransactionCurrency` | string | Document currency | +| ↳ `DocumentDate` | string | Document date \(Edm.DateTime\) | +| ↳ `CreationDate` | string | Creation date \(Edm.DateTime\) | +| ↳ `LastChangeDate` | string | Last change date \(Edm.DateTime\) | +| ↳ `to_DeliveryDocumentItem` | json | Delivery items \(when $expand=to_DeliveryDocumentItem\) | +| ↳ `to_DeliveryDocumentPartner` | json | Delivery partners \(when $expand=to_DeliveryDocumentPartner\) | ### `sap_s4hana_list_inbound_deliveries` @@ -576,10 +867,10 @@ List inbound deliveries from SAP S/4HANA Cloud (API_INBOUND_DELIVERY_SRV;v=0002, | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -598,7 +889,25 @@ List inbound deliveries from SAP S/4HANA Cloud (API_INBOUND_DELIVERY_SRV;v=0002, | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP | -| `data` | json | Array of A_InbDeliveryHeader entities | +| `data` | json | OData v2 response envelope; collection at output.data.d.results | +| ↳ `d` | json | OData v2 envelope | +| ↳ `results` | array | A_InbDeliveryHeader entities | +| ↳ `DeliveryDocument` | string | Inbound delivery number | +| ↳ `DeliveryDocumentType` | string | Delivery document type \(e.g., EL\) | +| ↳ `SDDocumentCategory` | string | SD document category \(e.g., 7 = inbound delivery\) | +| ↳ `ReceivingPlant` | string | Receiving plant | +| ↳ `Supplier` | string | Supplier business partner | +| ↳ `ShipToParty` | string | Ship-to business partner | +| ↳ `DeliveryDate` | string | Delivery date \(Edm.DateTime\) | +| ↳ `ActualGoodsMovementDate` | string | Actual goods movement \(receipt\) date \(Edm.DateTime\) | +| ↳ `PlannedGoodsMovementDate` | string | Planned goods movement date \(Edm.DateTime\) | +| ↳ `OverallSDProcessStatus` | string | Overall SD process \(delivery\) status | +| ↳ `OverallGoodsMovementStatus` | string | Overall goods movement status | +| ↳ `DocumentDate` | string | Document date \(Edm.DateTime\) | +| ↳ `CreationDate` | string | Creation date \(Edm.DateTime\) | +| ↳ `LastChangeDate` | string | Last change date \(Edm.DateTime\) | +| ↳ `__next` | string | OData skiptoken URL for next page | +| ↳ `__count` | string | Total count when $inlinecount=allpages is used | ### `sap_s4hana_get_inbound_delivery` @@ -608,10 +917,10 @@ Retrieve a single inbound delivery by DeliveryDocument key from SAP S/4HANA Clou | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -627,7 +936,24 @@ Retrieve a single inbound delivery by DeliveryDocument key from SAP S/4HANA Clou | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP | -| `data` | json | A_InbDeliveryHeader entity | +| `data` | json | OData v2 response envelope; entity at output.data.d | +| ↳ `d` | json | A_InbDeliveryHeader entity | +| ↳ `DeliveryDocument` | string | Inbound delivery number | +| ↳ `DeliveryDocumentType` | string | Delivery document type | +| ↳ `SDDocumentCategory` | string | SD document category \(e.g., 7 = inbound delivery\) | +| ↳ `ReceivingPlant` | string | Receiving plant | +| ↳ `Supplier` | string | Supplier business partner | +| ↳ `ShipToParty` | string | Ship-to business partner | +| ↳ `DeliveryDate` | string | Delivery date \(Edm.DateTime\) | +| ↳ `ActualGoodsMovementDate` | string | Actual goods movement \(receipt\) date \(Edm.DateTime\) | +| ↳ `PlannedGoodsMovementDate` | string | Planned goods movement date \(Edm.DateTime\) | +| ↳ `OverallSDProcessStatus` | string | Overall SD process \(delivery\) status | +| ↳ `OverallGoodsMovementStatus` | string | Overall goods movement status | +| ↳ `DocumentDate` | string | Document date \(Edm.DateTime\) | +| ↳ `CreationDate` | string | Creation date \(Edm.DateTime\) | +| ↳ `LastChangeDate` | string | Last change date \(Edm.DateTime\) | +| ↳ `to_DeliveryDocumentItem` | json | Delivery items \(when $expand=to_DeliveryDocumentItem\) | +| ↳ `to_DeliveryDocumentPartner` | json | Delivery partners \(when $expand=to_DeliveryDocumentPartner\) | ### `sap_s4hana_list_billing_documents` @@ -637,10 +963,10 @@ List billing documents (customer invoices) from SAP S/4HANA Cloud (API_BILLING_D | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -652,14 +978,47 @@ List billing documents (customer invoices) from SAP S/4HANA Cloud (API_BILLING_D | `skip` | number | No | Number of results to skip \($skip\) | | `orderBy` | string | No | OData $orderby expression | | `select` | string | No | Comma-separated fields to return \($select\) | -| `expand` | string | No | Comma-separated navigation properties to expand \(e.g., "to_Item,to_Partner"\) | +| `expand` | string | No | Comma-separated navigation properties to expand \(e.g., "to_Item,to_Partner,to_PricingElement"\) | #### Output | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP | -| `data` | json | Array of A_BillingDocument entities | +| `data` | json | OData v2 response envelope; collection at output.data.d.results | +| ↳ `d` | json | OData v2 envelope | +| ↳ `results` | array | A_BillingDocument entities | +| ↳ `BillingDocument` | string | Billing document number | +| ↳ `SDDocumentCategory` | string | SD document category | +| ↳ `BillingDocumentCategory` | string | Billing document category | +| ↳ `BillingDocumentType` | string | Billing document type \(e.g., F2\) | +| ↳ `BillingDocumentDate` | string | Billing document date \(OData /Date\(ms\)/\) | +| ↳ `BillingDocumentIsCancelled` | boolean | Whether the billing document is cancelled | +| ↳ `CancelledBillingDocument` | string | Cancelled billing document number | +| ↳ `TotalNetAmount` | string | Total net amount \(Edm.Decimal as string\) | +| ↳ `TaxAmount` | string | Tax amount \(Edm.Decimal as string\) | +| ↳ `TotalGrossAmount` | string | Total gross amount \(Edm.Decimal as string\) | +| ↳ `TransactionCurrency` | string | Document currency | +| ↳ `SoldToParty` | string | Sold-to business partner | +| ↳ `PayerParty` | string | Payer party | +| ↳ `SalesOrganization` | string | Sales organization | +| ↳ `DistributionChannel` | string | Distribution channel | +| ↳ `Division` | string | Division | +| ↳ `CompanyCode` | string | Company code | +| ↳ `FiscalYear` | string | Fiscal year | +| ↳ `OverallBillingStatus` | string | Overall billing status | +| ↳ `AccountingPostingStatus` | string | Accounting posting status | +| ↳ `AccountingTransferStatus` | string | Accounting transfer status | +| ↳ `InvoiceClearingStatus` | string | Invoice clearing status | +| ↳ `AccountingDocument` | string | Linked accounting document | +| ↳ `CustomerPaymentTerms` | string | Customer payment terms | +| ↳ `PaymentMethod` | string | Payment method | +| ↳ `DocumentReferenceID` | string | Document reference ID | +| ↳ `CreationDate` | string | Creation date \(OData /Date\(ms\)/\) | +| ↳ `LastChangeDate` | string | Last change date \(OData /Date\(ms\)/\) | +| ↳ `LastChangeDateTime` | string | Last change date-time \(Edm.DateTimeOffset\) | +| ↳ `__next` | string | OData skiptoken URL for next page | +| ↳ `__count` | string | Total count when $inlinecount=allpages is used | ### `sap_s4hana_get_billing_document` @@ -669,10 +1028,10 @@ Retrieve a single billing document (customer invoice) by BillingDocument key fro | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -681,14 +1040,47 @@ Retrieve a single billing document (customer invoice) by BillingDocument key fro | `password` | string | No | Password for HTTP Basic auth | | `billingDocument` | string | Yes | BillingDocument key \(string, up to 10 characters\) | | `select` | string | No | Comma-separated fields to return \($select\) | -| `expand` | string | No | Comma-separated navigation properties to expand \(e.g., "to_Item,to_Partner"\) | +| `expand` | string | No | Comma-separated navigation properties to expand \(e.g., "to_Item,to_Partner,to_PricingElement"\) | #### Output | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP | -| `data` | json | A_BillingDocument entity | +| `data` | json | OData v2 response envelope; entity at output.data.d | +| ↳ `d` | json | A_BillingDocument entity | +| ↳ `BillingDocument` | string | Billing document number | +| ↳ `SDDocumentCategory` | string | SD document category | +| ↳ `BillingDocumentCategory` | string | Billing document category | +| ↳ `BillingDocumentType` | string | Billing document type | +| ↳ `BillingDocumentDate` | string | Billing document date \(OData /Date\(ms\)/\) | +| ↳ `BillingDocumentIsCancelled` | boolean | Whether the billing document is cancelled | +| ↳ `CancelledBillingDocument` | string | Cancelled billing document number | +| ↳ `TotalNetAmount` | string | Total net amount \(Edm.Decimal as string\) | +| ↳ `TaxAmount` | string | Tax amount \(Edm.Decimal as string\) | +| ↳ `TotalGrossAmount` | string | Total gross amount \(Edm.Decimal as string\) | +| ↳ `TransactionCurrency` | string | Document currency | +| ↳ `SoldToParty` | string | Sold-to business partner | +| ↳ `PayerParty` | string | Payer party | +| ↳ `SalesOrganization` | string | Sales organization | +| ↳ `DistributionChannel` | string | Distribution channel | +| ↳ `Division` | string | Division | +| ↳ `CompanyCode` | string | Company code | +| ↳ `FiscalYear` | string | Fiscal year | +| ↳ `OverallBillingStatus` | string | Overall billing status | +| ↳ `AccountingPostingStatus` | string | Accounting posting status | +| ↳ `AccountingTransferStatus` | string | Accounting transfer status | +| ↳ `InvoiceClearingStatus` | string | Invoice clearing status | +| ↳ `AccountingDocument` | string | Linked accounting document | +| ↳ `CustomerPaymentTerms` | string | Customer payment terms | +| ↳ `PaymentMethod` | string | Payment method | +| ↳ `DocumentReferenceID` | string | Document reference ID | +| ↳ `CreationDate` | string | Creation date \(OData /Date\(ms\)/\) | +| ↳ `LastChangeDate` | string | Last change date \(OData /Date\(ms\)/\) | +| ↳ `LastChangeDateTime` | string | Last change date-time \(Edm.DateTimeOffset\) | +| ↳ `to_Item` | json | Billing document items \(when $expand=to_Item\) | +| ↳ `to_Partner` | json | Billing document partners \(when $expand=to_Partner\) | +| ↳ `to_PricingElement` | json | Billing document pricing elements \(when $expand=to_PricingElement\) | ### `sap_s4hana_list_products` @@ -698,10 +1090,10 @@ List products (materials) from SAP S/4HANA Cloud (API_PRODUCT_SRV, A_Product) wi | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -720,7 +1112,30 @@ List products (materials) from SAP S/4HANA Cloud (API_PRODUCT_SRV, A_Product) wi | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP | -| `data` | json | Array of A_Product entities | +| `data` | json | OData v2 response envelope; collection at output.data.d.results | +| ↳ `d` | json | OData v2 envelope | +| ↳ `results` | array | A_Product entities | +| ↳ `Product` | string | Product \(material\) number | +| ↳ `ProductType` | string | Product type \(e.g., FERT, HAWA\) | +| ↳ `ProductGroup` | string | Material group | +| ↳ `BaseUnit` | string | Base unit of measure | +| ↳ `Brand` | string | Brand | +| ↳ `Division` | string | Division | +| ↳ `GrossWeight` | string | Gross weight | +| ↳ `NetWeight` | string | Net weight | +| ↳ `WeightUnit` | string | Weight unit of measure | +| ↳ `CrossPlantStatus` | string | Cross-plant material status | +| ↳ `IsMarkedForDeletion` | boolean | Deletion flag | +| ↳ `ProductStandardID` | string | Standard product ID \(e.g., GTIN\) | +| ↳ `ItemCategoryGroup` | string | Item category group | +| ↳ `ProductOldID` | string | Legacy/old product ID | +| ↳ `CreatedByUser` | string | User who created the product | +| ↳ `CreationDate` | string | Creation date \(OData /Date\(ms\)/\) | +| ↳ `LastChangedByUser` | string | User who last changed the product | +| ↳ `LastChangeDate` | string | Last change date | +| ↳ `LastChangeDateTime` | string | Last change timestamp \(Edm.DateTimeOffset\) | +| ↳ `__next` | string | OData skiptoken URL for next page | +| ↳ `__count` | string | Total count when $inlinecount=allpages is used | ### `sap_s4hana_get_product` @@ -730,10 +1145,10 @@ Retrieve a single product (material) by Product key from SAP S/4HANA Cloud (API_ | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -749,20 +1164,43 @@ Retrieve a single product (material) by Product key from SAP S/4HANA Cloud (API_ | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP | -| `data` | json | A_Product entity | +| `data` | json | OData v2 response envelope; entity at output.data.d | +| ↳ `d` | json | A_Product entity | +| ↳ `Product` | string | Product \(material\) number | +| ↳ `ProductType` | string | Product type \(e.g., FERT, HAWA\) | +| ↳ `ProductGroup` | string | Material group | +| ↳ `BaseUnit` | string | Base unit of measure | +| ↳ `Brand` | string | Brand | +| ↳ `Division` | string | Division | +| ↳ `GrossWeight` | string | Gross weight | +| ↳ `NetWeight` | string | Net weight | +| ↳ `WeightUnit` | string | Weight unit of measure | +| ↳ `CrossPlantStatus` | string | Cross-plant material status | +| ↳ `IsMarkedForDeletion` | boolean | Deletion flag | +| ↳ `ProductStandardID` | string | Standard product ID \(e.g., GTIN\) | +| ↳ `ItemCategoryGroup` | string | Item category group | +| ↳ `ProductOldID` | string | Legacy/old product ID | +| ↳ `CreatedByUser` | string | User who created the product | +| ↳ `CreationDate` | string | Creation date \(OData /Date\(ms\)/\) | +| ↳ `LastChangedByUser` | string | User who last changed the product | +| ↳ `LastChangeDate` | string | Last change date | +| ↳ `LastChangeDateTime` | string | Last change timestamp \(Edm.DateTimeOffset\) | +| ↳ `to_Description` | json | Product descriptions \(when $expand=to_Description\) | +| ↳ `to_Plant` | json | Plant-level data \(when $expand=to_Plant\) | +| ↳ `to_ProductSales` | json | Sales data \(when $expand=to_ProductSales\) | ### `sap_s4hana_update_product` -Update fields on an A_Product entity in SAP S/4HANA Cloud (API_PRODUCT_SRV). PATCH only sends the fields you provide; existing values are preserved. Flat scalar header fields only — deep/multi-entity updates across navigation properties are not supported by API_PRODUCT_SRV PATCH/PUT (see SAP KBA 2833338); update child entities (plant, valuation, sales data, etc.) via their own endpoints. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET. +Update fields on an A_Product entity in SAP S/4HANA Cloud (API_PRODUCT_SRV). Uses HTTP MERGE (OData v2 partial update) — only the fields you provide are written; existing values are preserved. Flat scalar header fields only — deep/multi-entity updates across navigation properties are not supported by API_PRODUCT_SRV MERGE/PUT (see SAP KBA 2833338); update child entities (plant, valuation, sales data, etc.) via their own endpoints. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET. #### Input | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -778,7 +1216,14 @@ Update fields on an A_Product entity in SAP S/4HANA Cloud (API_PRODUCT_SRV). PAT | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP \(204 on success\) | -| `data` | json | Null on 204 success, or updated A_Product entity if SAP returns one | +| `data` | json | Null on 204 success, or OData v2 envelope with the updated A_Product entity at output.data.d | +| ↳ `d` | json | Updated A_Product entity \(only present if SAP returns a body\) | +| ↳ `Product` | string | Product \(material\) number | +| ↳ `ProductType` | string | Product type | +| ↳ `ProductGroup` | string | Material group | +| ↳ `BaseUnit` | string | Base unit of measure | +| ↳ `IsMarkedForDeletion` | boolean | Deletion flag | +| ↳ `LastChangeDate` | string | Last change date | ### `sap_s4hana_list_material_stock` @@ -788,10 +1233,10 @@ List material stock quantities from SAP S/4HANA Cloud (API_MATERIAL_STOCK_SRV, A | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -810,7 +1255,20 @@ List material stock quantities from SAP S/4HANA Cloud (API_MATERIAL_STOCK_SRV, A | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP | -| `data` | json | Array of A_MatlStkInAcctMod stock entries | +| `data` | json | OData payload containing the array of A_MatlStkInAcctMod stock entries | +| ↳ `Material` | string | Material number | +| ↳ `Plant` | string | Plant identifier | +| ↳ `StorageLocation` | string | Storage location identifier | +| ↳ `Batch` | string | Batch identifier | +| ↳ `Supplier` | string | Supplier business partner key | +| ↳ `Customer` | string | Customer business partner key | +| ↳ `WBSElementInternalID` | string | WBS element internal ID | +| ↳ `SDDocument` | string | SD document number | +| ↳ `SDDocumentItem` | string | SD document item | +| ↳ `InventorySpecialStockType` | string | Special stock type indicator | +| ↳ `InventoryStockType` | string | Stock type \(e.g., 01 unrestricted-use, 02 quality inspection, 03 blocked, 04 restricted-use\) | +| ↳ `MatlWrhsStkQtyInMatlBaseUnit` | string | Material warehouse stock quantity in material base unit \(Edm.Decimal serialized as string\) | +| ↳ `MaterialBaseUnit` | string | Material base unit of measure | ### `sap_s4hana_list_material_documents` @@ -820,10 +1278,10 @@ List material document headers (goods movements) from SAP S/4HANA Cloud (API_MAT | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -842,7 +1300,22 @@ List material document headers (goods movements) from SAP S/4HANA Cloud (API_MAT | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP | -| `data` | json | Array of A_MaterialDocumentHeader entities | +| `data` | json | OData payload containing the array of A_MaterialDocumentHeader entities | +| ↳ `MaterialDocumentYear` | string | Material document year \(4-digit fiscal year\) | +| ↳ `MaterialDocument` | string | Material document number | +| ↳ `DocumentDate` | string | Document date \(OData /Date\(...\)/ string\) | +| ↳ `PostingDate` | string | Posting date \(OData /Date\(...\)/ string\) | +| ↳ `MaterialDocumentHeaderText` | string | Header text describing the material document | +| ↳ `ReferenceDocument` | string | Reference document number | +| ↳ `GoodsMovementCode` | string | Goods movement code \(e.g., 01 GR for PO, 03 GI to cost center\) | +| ↳ `InventoryTransactionType` | string | Inventory transaction type indicator | +| ↳ `CreatedByUser` | string | User who created the material document | +| ↳ `CreationDate` | string | Creation date \(OData /Date\(...\)/ string\) | +| ↳ `CreationTime` | string | Creation time \(OData PT...S string\) | +| ↳ `VersionForPrintingSlip` | string | Version for printing the goods movement slip | +| ↳ `ManualPrintIsTriggered` | boolean | Indicates whether manual print was triggered for this document | +| ↳ `CtrlPostgForExtWhseMgmtSyst` | string | Control posting for external warehouse management system | +| ↳ `to_MaterialDocumentItem` | json | Material document items \(only present when $expand=to_MaterialDocumentItem is supplied\) | ### `sap_s4hana_get_material_document` @@ -852,10 +1325,10 @@ Retrieve a single material document header by composite key (MaterialDocument + | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -872,7 +1345,22 @@ Retrieve a single material document header by composite key (MaterialDocument + | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP | -| `data` | json | A_MaterialDocumentHeader entity | +| `data` | json | OData payload containing the A_MaterialDocumentHeader entity \(and optionally to_MaterialDocumentItem when expanded\) | +| ↳ `MaterialDocumentYear` | string | Material document year \(4-digit fiscal year\) | +| ↳ `MaterialDocument` | string | Material document number | +| ↳ `DocumentDate` | string | Document date \(OData /Date\(...\)/ string\) | +| ↳ `PostingDate` | string | Posting date \(OData /Date\(...\)/ string\) | +| ↳ `MaterialDocumentHeaderText` | string | Header text describing the material document | +| ↳ `ReferenceDocument` | string | Reference document number | +| ↳ `GoodsMovementCode` | string | Goods movement code \(e.g., 01 GR for PO, 03 GI to cost center\) | +| ↳ `InventoryTransactionType` | string | Inventory transaction type indicator | +| ↳ `CreatedByUser` | string | User who created the material document | +| ↳ `CreationDate` | string | Creation date \(OData /Date\(...\)/ string\) | +| ↳ `CreationTime` | string | Creation time \(OData PT...S string\) | +| ↳ `VersionForPrintingSlip` | string | Version for printing the goods movement slip | +| ↳ `ManualPrintIsTriggered` | boolean | Indicates whether manual print was triggered for this document | +| ↳ `CtrlPostgForExtWhseMgmtSyst` | string | Control posting for external warehouse management system | +| ↳ `to_MaterialDocumentItem` | json | Material document items \(only present when $expand=to_MaterialDocumentItem is supplied\) | ### `sap_s4hana_list_purchase_requisitions` @@ -882,10 +1370,10 @@ List purchase requisitions from SAP S/4HANA Cloud (API_PURCHASEREQ_PROCESS_SRV, | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -904,7 +1392,14 @@ List purchase requisitions from SAP S/4HANA Cloud (API_PURCHASEREQ_PROCESS_SRV, | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP | -| `data` | json | Array of A_PurchaseRequisitionHeader entities | +| `data` | json | OData v2 response envelope; collection at output.data.d.results | +| ↳ `d` | json | OData v2 envelope | +| ↳ `results` | array | A_PurchaseRequisitionHeader entities | +| ↳ `PurchaseRequisition` | string | Purchase requisition number | +| ↳ `PurchaseRequisitionType` | string | Purchase requisition document type \(e.g., NB\) | +| ↳ `PurReqnDescription` | string | Purchase requisition description | +| ↳ `SourceDetermination` | string | Source-of-supply determination flag | +| ↳ `__next` | string | OData skiptoken URL for next page | ### `sap_s4hana_get_purchase_requisition` @@ -914,10 +1409,10 @@ Retrieve a single purchase requisition by PurchaseRequisition key from SAP S/4HA | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -933,7 +1428,13 @@ Retrieve a single purchase requisition by PurchaseRequisition key from SAP S/4HA | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP | -| `data` | json | A_PurchaseRequisitionHeader entity | +| `data` | json | OData v2 response envelope; entity at output.data.d | +| ↳ `d` | json | A_PurchaseRequisitionHeader entity | +| ↳ `PurchaseRequisition` | string | Purchase requisition number | +| ↳ `PurchaseRequisitionType` | string | PR document type \(e.g., NB\) | +| ↳ `PurReqnDescription` | string | Purchase requisition description | +| ↳ `SourceDetermination` | string | Source-of-supply determination flag | +| ↳ `to_PurchaseReqnItem` | json | Expanded PR items \(when $expand=to_PurchaseReqnItem\) | ### `sap_s4hana_create_purchase_requisition` @@ -943,10 +1444,10 @@ Create a purchase requisition in SAP S/4HANA Cloud (API_PURCHASEREQ_PROCESS_SRV, | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -955,27 +1456,33 @@ Create a purchase requisition in SAP S/4HANA Cloud (API_PURCHASEREQ_PROCESS_SRV, | `password` | string | No | Password for HTTP Basic auth | | `purchaseRequisitionType` | string | Yes | PurchaseRequisitionType \(e.g., "NB" Standard PR\) | | `items` | json | Yes | to_PurchaseReqnItem deep-insert array \(e.g., \[\{"PurchaseRequisitionItem":"10","Material":"TG11","RequestedQuantity":"5","Plant":"1010","BaseUnit":"PC","DeliveryDate":"/Date\(1735689600000\)/"\}\]\) | -| `body` | json | No | Additional A_PurchaseRequisitionHeader fields merged into the create payload \(e.g., \{"PurchaseRequisitionDescription":"Office supplies"\}\) | +| `body` | json | No | Additional A_PurchaseRequisitionHeader fields merged into the create payload \(e.g., \{"PurReqnDescription":"Office supplies"\}\) | #### Output | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP | -| `data` | json | Created A_PurchaseRequisitionHeader entity | +| `data` | json | OData v2 response envelope; created entity at output.data.d | +| ↳ `d` | json | Created A_PurchaseRequisitionHeader entity | +| ↳ `PurchaseRequisition` | string | Auto-assigned purchase requisition number | +| ↳ `PurchaseRequisitionType` | string | PR document type \(e.g., NB\) | +| ↳ `PurReqnDescription` | string | Purchase requisition description | +| ↳ `SourceDetermination` | string | Source-of-supply determination flag | +| ↳ `to_PurchaseReqnItem` | json | Created PR items returned in deep insert | ### `sap_s4hana_update_purchase_requisition` -Update fields on an A_PurchaseRequisitionHeader entity in SAP S/4HANA Cloud (API_PURCHASEREQ_PROCESS_SRV; deprecated since S/4HANA 2402, successor is API_PURCHASEREQUISITION_2 OData v4). PATCH only sends the fields you provide; existing values are preserved. If-Match defaults to a wildcard - for safe concurrent updates pass the ETag from a prior GET to avoid lost updates. +Update fields on an A_PurchaseRequisitionHeader entity in SAP S/4HANA Cloud (API_PURCHASEREQ_PROCESS_SRV; deprecated since S/4HANA 2402, successor is API_PURCHASEREQUISITION_2 OData v4). Uses HTTP MERGE (OData v2 partial update) — only the fields you provide are written; existing values are preserved. Header-only — deep updates across navigations are not supported (SAP KBA 2833338); use the A_PurchaseReqnItem entity directly to modify items. If-Match defaults to a wildcard - for safe concurrent updates pass the ETag from a prior GET to avoid lost updates. #### Input | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -991,7 +1498,12 @@ Update fields on an A_PurchaseRequisitionHeader entity in SAP S/4HANA Cloud (API | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP \(204 on success\) | -| `data` | json | Null on 204 success, or updated A_PurchaseRequisitionHeader entity if SAP returns one | +| `data` | json | Null on 204 success, or OData v2 envelope with updated A_PurchaseRequisitionHeader at output.data.d | +| ↳ `d` | json | Updated A_PurchaseRequisitionHeader entity \(if returned\) | +| ↳ `PurchaseRequisition` | string | Purchase requisition number | +| ↳ `PurchaseRequisitionType` | string | PR document type | +| ↳ `PurReqnDescription` | string | Purchase requisition description | +| ↳ `SourceDetermination` | string | Source-of-supply determination flag | ### `sap_s4hana_list_purchase_orders` @@ -1001,10 +1513,10 @@ List purchase orders from SAP S/4HANA Cloud (API_PURCHASEORDER_PROCESS_SRV, A_Pu | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -1023,7 +1535,22 @@ List purchase orders from SAP S/4HANA Cloud (API_PURCHASEORDER_PROCESS_SRV, A_Pu | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP | -| `data` | json | Array of A_PurchaseOrder entities | +| `data` | json | OData v2 response envelope; collection at output.data.d.results | +| ↳ `d` | json | OData v2 envelope | +| ↳ `results` | array | A_PurchaseOrder entities | +| ↳ `PurchaseOrder` | string | Purchase order number | +| ↳ `PurchaseOrderType` | string | PO document type \(e.g., NB\) | +| ↳ `CompanyCode` | string | Company code | +| ↳ `PurchasingOrganization` | string | Purchasing organization | +| ↳ `PurchasingGroup` | string | Purchasing group | +| ↳ `Supplier` | string | Supplier business partner key | +| ↳ `DocumentCurrency` | string | Document currency | +| ↳ `NetAmount` | string | Net amount of the purchase order | +| ↳ `CreationDate` | string | Creation date \(OData /Date\(ms\)/\) | +| ↳ `CreatedByUser` | string | User who created the PO | +| ↳ `PurchaseOrderDate` | string | Purchase order date | +| ↳ `__next` | string | OData skiptoken URL for next page | +| ↳ `__count` | string | Total count when $inlinecount=allpages is used | ### `sap_s4hana_get_purchase_order` @@ -1033,10 +1560,10 @@ Retrieve a single purchase order by PurchaseOrder key from SAP S/4HANA Cloud (AP | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -1052,7 +1579,25 @@ Retrieve a single purchase order by PurchaseOrder key from SAP S/4HANA Cloud (AP | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP | -| `data` | json | A_PurchaseOrder entity | +| `data` | json | OData v2 response envelope; entity at output.data.d | +| ↳ `d` | json | A_PurchaseOrder entity | +| ↳ `PurchaseOrder` | string | Purchase order number | +| ↳ `PurchaseOrderType` | string | PO document type | +| ↳ `CompanyCode` | string | Company code | +| ↳ `PurchasingOrganization` | string | Purchasing organization | +| ↳ `PurchasingGroup` | string | Purchasing group | +| ↳ `Supplier` | string | Supplier business partner key | +| ↳ `DocumentCurrency` | string | Document currency | +| ↳ `NetAmount` | string | Net amount of the purchase order | +| ↳ `CreationDate` | string | Creation date \(OData /Date\(ms\)/\) | +| ↳ `CreatedByUser` | string | User who created the PO | +| ↳ `PurchaseOrderDate` | string | Purchase order date | +| ↳ `ValidityStartDate` | string | Validity start date | +| ↳ `ValidityEndDate` | string | Validity end date | +| ↳ `IncotermsClassification` | string | Incoterms classification \(e.g., FOB\) | +| ↳ `PaymentTerms` | string | Payment terms key | +| ↳ `LastChangeDateTime` | string | Last change timestamp \(OData /Date\(ms\)/\) | +| ↳ `to_PurchaseOrderItem` | json | Expanded PO items \(when $expand=to_PurchaseOrderItem\) | ### `sap_s4hana_create_purchase_order` @@ -1062,10 +1607,10 @@ Create a purchase order in SAP S/4HANA Cloud (API_PURCHASEORDER_PROCESS_SRV, A_P | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -1084,20 +1629,31 @@ Create a purchase order in SAP S/4HANA Cloud (API_PURCHASEORDER_PROCESS_SRV, A_P | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP | -| `data` | json | Created A_PurchaseOrder entity | +| `data` | json | OData v2 response envelope; created entity at output.data.d | +| ↳ `d` | json | Created A_PurchaseOrder entity | +| ↳ `PurchaseOrder` | string | Auto-assigned purchase order number | +| ↳ `PurchaseOrderType` | string | PO document type | +| ↳ `CompanyCode` | string | Company code | +| ↳ `PurchasingOrganization` | string | Purchasing organization | +| ↳ `PurchasingGroup` | string | Purchasing group | +| ↳ `Supplier` | string | Supplier business partner key | +| ↳ `DocumentCurrency` | string | Document currency | +| ↳ `NetAmount` | string | Net amount of the purchase order | +| ↳ `CreationDate` | string | Creation date \(OData /Date\(ms\)/\) | +| ↳ `to_PurchaseOrderItem` | json | Created PO items returned in deep insert | ### `sap_s4hana_update_purchase_order` -Update fields on an A_PurchaseOrder entity in SAP S/4HANA Cloud (API_PURCHASEORDER_PROCESS_SRV). PATCH only sends the fields you provide; existing values are preserved. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET to avoid lost updates. +Update fields on an A_PurchaseOrder header in SAP S/4HANA Cloud (API_PURCHASEORDER_PROCESS_SRV). Uses HTTP MERGE (OData v2 partial update) — only the fields you provide are written; existing values are preserved. Header-only — line-item changes are not supported via deep update on the header (SAP KBA 2833338); use the A_PurchaseOrderItem entity directly to modify items. If-Match defaults to a wildcard - for safe concurrent updates pass the ETag from a prior GET to avoid lost updates. #### Input | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -1113,7 +1669,16 @@ Update fields on an A_PurchaseOrder entity in SAP S/4HANA Cloud (API_PURCHASEORD | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP \(204 on success\) | -| `data` | json | Null on 204 success, or updated A_PurchaseOrder entity if SAP returns one | +| `data` | json | Null on 204 success, or OData v2 envelope with updated A_PurchaseOrder at output.data.d | +| ↳ `d` | json | Updated A_PurchaseOrder entity \(if returned\) | +| ↳ `PurchaseOrder` | string | Purchase order number | +| ↳ `PurchaseOrderType` | string | PO document type | +| ↳ `CompanyCode` | string | Company code | +| ↳ `PurchasingGroup` | string | Purchasing group | +| ↳ `Supplier` | string | Supplier key | +| ↳ `NetAmount` | string | Net amount | +| ↳ `DocumentCurrency` | string | Document currency | +| ↳ `LastChangeDateTime` | string | Last change timestamp | ### `sap_s4hana_list_supplier_invoices` @@ -1123,10 +1688,10 @@ List supplier invoices from SAP S/4HANA Cloud (API_SUPPLIERINVOICE_PROCESS_SRV, | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -1145,7 +1710,26 @@ List supplier invoices from SAP S/4HANA Cloud (API_SUPPLIERINVOICE_PROCESS_SRV, | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP | -| `data` | json | Array of A_SupplierInvoice entities | +| `data` | json | OData v2 response envelope; collection at output.data.d.results | +| ↳ `d` | json | OData v2 envelope | +| ↳ `results` | array | A_SupplierInvoice entities | +| ↳ `SupplierInvoice` | string | Supplier invoice number | +| ↳ `FiscalYear` | string | Fiscal year | +| ↳ `CompanyCode` | string | Company code | +| ↳ `DocumentDate` | string | Invoice document date | +| ↳ `PostingDate` | string | Posting date | +| ↳ `InvoicingParty` | string | Invoicing party \(supplier key\) | +| ↳ `InvoiceGrossAmount` | string | Gross invoice amount | +| ↳ `DocumentCurrency` | string | Document currency | +| ↳ `AccountingDocumentType` | string | Accounting document type | +| ↳ `PaymentTerms` | string | Payment terms key | +| ↳ `DueCalculationBaseDate` | string | Baseline date for due-date calculation | +| ↳ `SupplierInvoiceIDByInvcgParty` | string | Reference number used by the invoicing party | +| ↳ `PaymentMethod` | string | Payment method | +| ↳ `TaxIsCalculatedAutomatically` | boolean | Whether tax is calculated automatically | +| ↳ `ManualCashDiscount` | string | Manually entered cash discount amount | +| ↳ `BusinessPlace` | string | Business place \(jurisdiction code\) | +| ↳ `__next` | string | OData skiptoken URL for next page | ### `sap_s4hana_get_supplier_invoice` @@ -1155,10 +1739,10 @@ Retrieve a single supplier invoice by composite key (SupplierInvoice + FiscalYea | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | @@ -1175,20 +1759,37 @@ Retrieve a single supplier invoice by composite key (SupplierInvoice + FiscalYea | Parameter | Type | Description | | --------- | ---- | ----------- | | `status` | number | HTTP status code returned by SAP | -| `data` | json | A_SupplierInvoice entity | +| `data` | json | OData v2 response envelope; entity at output.data.d | +| ↳ `d` | json | A_SupplierInvoice entity | +| ↳ `SupplierInvoice` | string | Supplier invoice number | +| ↳ `FiscalYear` | string | Fiscal year | +| ↳ `CompanyCode` | string | Company code | +| ↳ `DocumentDate` | string | Invoice document date | +| ↳ `PostingDate` | string | Posting date | +| ↳ `InvoicingParty` | string | Invoicing party \(supplier key\) | +| ↳ `InvoiceGrossAmount` | string | Gross invoice amount | +| ↳ `DocumentCurrency` | string | Document currency | +| ↳ `AccountingDocumentType` | string | Accounting document type | +| ↳ `PaymentTerms` | string | Payment terms key | +| ↳ `DueCalculationBaseDate` | string | Baseline date for due-date calculation | +| ↳ `SupplierInvoiceIDByInvcgParty` | string | Reference number used by the invoicing party | +| ↳ `PaymentMethod` | string | Payment method | +| ↳ `TaxIsCalculatedAutomatically` | boolean | Whether tax is calculated automatically | +| ↳ `ManualCashDiscount` | string | Manually entered cash discount amount | +| ↳ `BusinessPlace` | string | Business place \(jurisdiction code\) | ### `sap_s4hana_odata_query` -Make an arbitrary OData v2 call against any SAP S/4HANA Cloud whitelisted Communication Scenario. Use when no dedicated tool exists for the entity. The proxy handles auth, CSRF, and OData unwrapping. +Make an arbitrary OData v2 call against any SAP S/4HANA Cloud whitelisted Communication Scenario. Use when no dedicated tool exists for the entity. The proxy handles auth, CSRF, and OData unwrapping. For write operations (POST/PUT/PATCH/MERGE/DELETE), pass an If-Match ETag obtained from a prior GET to avoid lost updates; misuse will mutate production data. #### Input | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `subdomain` | string | Yes | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | -| `region` | string | Yes | BTP region \(e.g. eu10, us10\) | -| `clientId` | string | Yes | OAuth client ID from the S/4HANA Communication Arrangement | -| `clientSecret` | string | Yes | OAuth client secret from the S/4HANA Communication Arrangement | +| `subdomain` | string | No | SAP BTP subaccount subdomain \(technical name of your subaccount, not the S/4HANA host\) | +| `region` | string | No | BTP region \(e.g. eu10, us10\) | +| `clientId` | string | No | OAuth client ID from the S/4HANA Communication Arrangement | +| `clientSecret` | string | No | OAuth client secret from the S/4HANA Communication Arrangement | | `deploymentType` | string | No | Deployment type: cloud_public \(default\), cloud_private, or on_premise | | `authType` | string | No | Authentication type: oauth_client_credentials \(default\) or basic | | `baseUrl` | string | No | Base URL of the S/4HANA host \(Cloud Private / On-Premise\) | diff --git a/apps/docs/content/docs/en/tools/sharepoint.mdx b/apps/docs/content/docs/en/tools/sharepoint.mdx index 13027ea8261..05a425ba35e 100644 --- a/apps/docs/content/docs/en/tools/sharepoint.mdx +++ b/apps/docs/content/docs/en/tools/sharepoint.mdx @@ -1,12 +1,12 @@ --- -title: Sharepoint +title: SharePoint description: Work with pages and lists --- import { BlockInfoCard } from "@/components/ui/block-info-card" @@ -73,6 +73,7 @@ Read a specific page from a SharePoint site | `pageId` | string | No | The ID of the page to read. Example: a GUID like 12345678-1234-1234-1234-123456789012 | | `pageName` | string | No | The name of the page to read \(alternative to pageId\). Example: Home.aspx or About-Us.aspx | | `maxPages` | number | No | Maximum number of pages to return when listing all pages \(default: 10, max: 50\) | +| `nextPageUrl` | string | No | Full @odata.nextLink URL from a previous Microsoft Graph page response | #### Output @@ -84,6 +85,7 @@ Read a specific page from a SharePoint site | ↳ `title` | string | The title of the page | | ↳ `webUrl` | string | The URL to access the page | | ↳ `pageLayout` | string | The layout type of the page | +| ↳ `description` | string | The description of the page | | ↳ `createdDateTime` | string | When the page was created | | ↳ `lastModifiedDateTime` | string | When the page was last modified | | `pages` | array | List of SharePoint pages | @@ -93,6 +95,7 @@ Read a specific page from a SharePoint site | ↳ `title` | string | The title of the page | | ↳ `webUrl` | string | The URL to access the page | | ↳ `pageLayout` | string | The layout type of the page | +| ↳ `description` | string | The description of the page | | ↳ `createdDateTime` | string | When the page was created | | ↳ `lastModifiedDateTime` | string | When the page was last modified | | ↳ `content` | object | content output from the tool | @@ -102,6 +105,7 @@ Read a specific page from a SharePoint site | ↳ `content` | string | Extracted text content from the page | | ↳ `canvasLayout` | object | Raw SharePoint canvas layout structure | | `totalPages` | number | Total number of pages found | +| `nextPageUrl` | string | Full Microsoft Graph @odata.nextLink URL for the next page of results | ### `sharepoint_list_sites` @@ -112,7 +116,9 @@ List details of all SharePoint sites | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `siteSelector` | string | No | Select the SharePoint site | +| `siteId` | string | No | The ID of the SharePoint site \(internal use\) | | `groupId` | string | No | The group ID for accessing a group team site. Example: a GUID like 12345678-1234-1234-1234-123456789012 | +| `nextPageUrl` | string | No | Full @odata.nextLink URL from a previous Microsoft Graph page response | #### Output @@ -139,6 +145,7 @@ List details of all SharePoint sites | ↳ `description` | string | The description of the site | | ↳ `createdDateTime` | string | When the site was created | | ↳ `lastModifiedDateTime` | string | When the site was last modified | +| `nextPageUrl` | string | Full Microsoft Graph @odata.nextLink URL for the next page of results | ### `sharepoint_create_list` @@ -153,7 +160,7 @@ Create a new list in a SharePoint site | `listDisplayName` | string | Yes | Display name of the list to create. Example: Project Tasks or Customer Contacts | | `listDescription` | string | No | Description of the list | | `listTemplate` | string | No | List template name \(e.g., 'genericList'\) | -| `pageContent` | string | No | Optional JSON of columns. Either a top-level array of column definitions or an object with \{ columns: \[...\] \}. | +| `pageContent` | json | No | Optional JSON of columns. Either a top-level array of column definitions or an object with \{ columns: \[...\] \}. | #### Output @@ -179,6 +186,9 @@ Get metadata (and optionally columns/items) for a SharePoint list | `siteSelector` | string | No | Select the SharePoint site | | `siteId` | string | No | The ID of the SharePoint site \(internal use\) | | `listId` | string | No | The ID of the list to retrieve. Example: b!abc123def456 or a GUID like 12345678-1234-1234-1234-123456789012 | +| `includeColumns` | boolean | No | Whether to include column definitions when retrieving a specific list | +| `includeItems` | boolean | No | Whether to include list items when retrieving a specific list | +| `nextPageUrl` | string | No | Full @odata.nextLink URL from a previous Microsoft Graph page response | #### Output @@ -194,6 +204,10 @@ Get metadata (and optionally columns/items) for a SharePoint list | ↳ `list` | object | List properties \(e.g., template\) | | ↳ `columns` | array | List column definitions | | `lists` | array | All lists in the site when no listId/title provided | +| `items` | array | List items with expanded fields when reading list items | +| ↳ `id` | string | Item ID | +| ↳ `fields` | object | Field values for the item | +| `nextPageUrl` | string | Full Microsoft Graph @odata.nextLink URL for the next page of results | ### `sharepoint_update_list` @@ -205,9 +219,9 @@ Update the properties (fields) on a SharePoint list item | --------- | ---- | -------- | ----------- | | `siteSelector` | string | No | Select the SharePoint site | | `siteId` | string | No | The ID of the SharePoint site \(internal use\) | -| `listId` | string | No | The ID of the list containing the item. Example: b!abc123def456 or a GUID like 12345678-1234-1234-1234-123456789012 | +| `listId` | string | Yes | The ID of the list containing the item. Example: b!abc123def456 or a GUID like 12345678-1234-1234-1234-123456789012 | | `itemId` | string | Yes | The ID of the list item to update. Example: 1, 42, or 123 | -| `listItemFields` | object | Yes | Field values to update on the list item | +| `listItemFields` | json | Yes | Field values to update on the list item | #### Output @@ -228,7 +242,7 @@ Add a new item to a SharePoint list | `siteSelector` | string | No | Select the SharePoint site | | `siteId` | string | No | The ID of the SharePoint site \(internal use\) | | `listId` | string | Yes | The ID of the list to add the item to. Example: b!abc123def456 or a GUID like 12345678-1234-1234-1234-123456789012 | -| `listItemFields` | object | Yes | Field values for the new list item | +| `listItemFields` | json | Yes | Field values for the new list item | #### Output @@ -250,7 +264,7 @@ Upload files to a SharePoint document library | `driveId` | string | No | The ID of the document library \(drive\). If not provided, uses default drive. Example: b!abc123def456 | | `folderPath` | string | No | Optional folder path within the document library. Example: /Documents/Subfolder or /Shared Documents/Reports | | `fileName` | string | No | Optional: override the uploaded file name. Example: report-2024.pdf | -| `files` | file[] | No | Files to upload to SharePoint | +| `files` | file[] | Yes | Files to upload to SharePoint | #### Output @@ -264,5 +278,15 @@ Upload files to a SharePoint document library | ↳ `createdDateTime` | string | When the file was created | | ↳ `lastModifiedDateTime` | string | When the file was last modified | | `fileCount` | number | Number of files uploaded | +| `skippedFiles` | array | Files that were skipped before upload | +| ↳ `name` | string | File name | +| ↳ `size` | number | File size in bytes | +| ↳ `limit` | number | Upload size limit in bytes | +| ↳ `reason` | string | Reason the file was skipped | +| `skippedCount` | number | Number of files skipped | +| `errors` | array | Per-file upload errors | +| ↳ `name` | string | File name | +| ↳ `error` | string | Error message | +| ↳ `status` | number | HTTP status from Microsoft Graph | diff --git a/apps/docs/content/docs/en/triggers/emailbison.mdx b/apps/docs/content/docs/en/triggers/emailbison.mdx new file mode 100644 index 00000000000..94a8c041659 --- /dev/null +++ b/apps/docs/content/docs/en/triggers/emailbison.mdx @@ -0,0 +1,1178 @@ +--- +title: Emailbison +description: Available Emailbison triggers for automating workflows +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +Emailbison provides 17 triggers for automating workflows based on events. + +## Triggers + +### Email Bison Contact First Emailed + +Trigger when a contact receives their first campaign email in Email Bison + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `scheduledEmail` | object | scheduledEmail output from the tool | +| ↳ `id` | number | Scheduled email ID | +| ↳ `lead_id` | number | Lead ID | +| ↳ `sequence_step_id` | number | Sequence step ID | +| ↳ `sequence_step_order` | number | Sequence step order | +| ↳ `sequence_step_variant` | number | Sequence step variant | +| ↳ `email_subject` | string | Email subject | +| ↳ `email_body` | string | Email body HTML | +| ↳ `status` | string | Scheduled email status | +| ↳ `scheduled_date_est` | string | Scheduled date in EST | +| ↳ `scheduled_date_local` | string | Scheduled date in local timezone | +| ↳ `local_timezone` | string | Scheduled email local timezone | +| ↳ `sent_at` | string | Email sent timestamp | +| ↳ `opens` | number | Open count | +| ↳ `replies` | number | Reply count | +| ↳ `unique_opens` | number | Unique open count | +| ↳ `unique_replies` | number | Unique reply count | +| ↳ `interested` | string | Interested status | +| ↳ `raw_message_id` | string | Raw email message ID | +| `campaignEvent` | object | campaignEvent output from the tool | +| ↳ `id` | number | Campaign event ID | +| ↳ `event_type` | string | Campaign event type | +| ↳ `created_at_local` | string | Campaign event local creation timestamp | +| ↳ `local_timezone` | string | Campaign event local timezone | +| ↳ `created_at` | string | Campaign event creation timestamp | +| `lead` | object | lead output from the tool | +| ↳ `id` | number | Lead ID | +| ↳ `email` | string | Lead email address | +| ↳ `first_name` | string | Lead first name | +| ↳ `last_name` | string | Lead last name | +| ↳ `status` | string | Lead status | +| ↳ `title` | string | Lead title | +| ↳ `company` | string | Lead company | +| ↳ `custom_variables` | json | Lead custom variables | +| ↳ `emails_sent` | number | Lead emails sent count | +| ↳ `opens` | number | Lead open count | +| ↳ `unique_opens` | number | Lead unique open count | +| ↳ `replies` | number | Lead reply count | +| ↳ `unique_replies` | number | Lead unique reply count | +| ↳ `bounces` | number | Lead bounce count | +| `campaign` | object | campaign output from the tool | +| ↳ `id` | number | Campaign ID | +| ↳ `name` | string | Campaign name | +| `senderEmail` | object | senderEmail output from the tool | +| ↳ `id` | number | Sender email ID | +| ↳ `name` | string | Sender email name | +| ↳ `email` | string | Sender email address | +| ↳ `status` | string | Sender email status | +| ↳ `account_type` | string | Sender email connection type | +| ↳ `daily_limit` | number | Sender email daily limit | +| ↳ `emails_sent` | number | Sender email sent count | +| ↳ `replied` | number | Sender email replied count | +| ↳ `opened` | number | Sender email opened count | +| ↳ `unsubscribed` | number | Sender email unsubscribed count | +| ↳ `bounced` | number | Sender email bounced count | +| ↳ `unique_replies` | number | Sender email unique reply count | +| ↳ `unique_opens` | number | Sender email unique open count | +| ↳ `total_leads_contacted` | number | Sender email total leads contacted | +| ↳ `interested` | number | Sender email interested count | +| ↳ `created_at` | string | Sender email creation timestamp | +| ↳ `updated_at` | string | Sender email update timestamp | + + +--- + +### Email Bison Contact Interested + +Trigger when a reply is marked interested in Email Bison + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `reply` | object | reply output from the tool | +| ↳ `id` | number | Reply ID | +| ↳ `uuid` | string | Reply UUID | +| ↳ `email_subject` | string | Reply email subject | +| ↳ `interested` | boolean | Whether the reply is marked interested | +| ↳ `automated_reply` | boolean | Whether the reply is automated | +| ↳ `html_body` | string | Reply HTML body | +| ↳ `text_body` | string | Reply plain text body | +| ↳ `raw_body` | string | Raw MIME reply body | +| ↳ `headers` | string | Encoded raw email headers | +| ↳ `date_received` | string | Reply received timestamp | +| ↳ `from_name` | string | Reply sender name | +| ↳ `from_email_address` | string | Reply sender email address | +| ↳ `primary_to_email_address` | string | Primary recipient email address | +| ↳ `to` | json | Reply To recipients | +| ↳ `cc` | json | Reply CC recipients | +| ↳ `bcc` | json | Reply BCC recipients | +| ↳ `parent_id` | number | Parent reply ID | +| ↳ `reply_type` | string | Reply type | +| ↳ `folder` | string | Reply folder | +| ↳ `raw_message_id` | string | Raw email message ID | +| ↳ `created_at` | string | Reply creation timestamp | +| ↳ `updated_at` | string | Reply update timestamp | +| ↳ `attachments` | json | Reply attachments | +| `campaignEvent` | object | campaignEvent output from the tool | +| ↳ `id` | number | Campaign event ID | +| ↳ `event_type` | string | Campaign event type | +| ↳ `created_at_local` | string | Campaign event local creation timestamp | +| ↳ `local_timezone` | string | Campaign event local timezone | +| ↳ `created_at` | string | Campaign event creation timestamp | +| `lead` | object | lead output from the tool | +| ↳ `id` | number | Lead ID | +| ↳ `email` | string | Lead email address | +| ↳ `first_name` | string | Lead first name | +| ↳ `last_name` | string | Lead last name | +| ↳ `status` | string | Lead status | +| ↳ `title` | string | Lead title | +| ↳ `company` | string | Lead company | +| ↳ `custom_variables` | json | Lead custom variables | +| ↳ `emails_sent` | number | Lead emails sent count | +| ↳ `opens` | number | Lead open count | +| ↳ `unique_opens` | number | Lead unique open count | +| ↳ `replies` | number | Lead reply count | +| ↳ `unique_replies` | number | Lead unique reply count | +| ↳ `bounces` | number | Lead bounce count | +| `campaign` | object | campaign output from the tool | +| ↳ `id` | number | Campaign ID | +| ↳ `name` | string | Campaign name | +| `scheduledEmail` | object | scheduledEmail output from the tool | +| ↳ `id` | number | Scheduled email ID | +| ↳ `sequence_step_id` | number | Sequence step ID | +| ↳ `sequence_step_order` | number | Sequence step order | +| ↳ `sequence_step_variant` | number | Sequence step variant | +| ↳ `status` | string | Scheduled email status | +| ↳ `scheduled_date_est` | string | Scheduled date in EST | +| ↳ `scheduled_date_local` | string | Scheduled date in local timezone | +| ↳ `local_timezone` | string | Scheduled email local timezone | +| ↳ `sent_at` | string | Email sent timestamp | +| ↳ `opens` | number | Open count | +| ↳ `replies` | number | Reply count | +| ↳ `unique_opens` | number | Unique open count | +| ↳ `unique_replies` | number | Unique reply count | +| ↳ `interested` | string | Interested status | +| ↳ `raw_message_id` | string | Raw email message ID | +| `senderEmail` | object | senderEmail output from the tool | +| ↳ `id` | number | Sender email ID | +| ↳ `name` | string | Sender email name | +| ↳ `email` | string | Sender email address | +| ↳ `status` | string | Sender email status | +| ↳ `account_type` | string | Sender email connection type | +| ↳ `daily_limit` | number | Sender email daily limit | +| ↳ `emails_sent` | number | Sender email sent count | +| ↳ `replied` | number | Sender email replied count | +| ↳ `opened` | number | Sender email opened count | +| ↳ `unsubscribed` | number | Sender email unsubscribed count | +| ↳ `bounced` | number | Sender email bounced count | +| ↳ `unique_replies` | number | Sender email unique reply count | +| ↳ `unique_opens` | number | Sender email unique open count | +| ↳ `total_leads_contacted` | number | Sender email total leads contacted | +| ↳ `interested` | number | Sender email interested count | +| ↳ `created_at` | string | Sender email creation timestamp | +| ↳ `updated_at` | string | Sender email update timestamp | + + +--- + +### Email Bison Contact Replied + +Trigger when a campaign lead replies in Email Bison + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `reply` | object | reply output from the tool | +| ↳ `id` | number | Reply ID | +| ↳ `uuid` | string | Reply UUID | +| ↳ `email_subject` | string | Reply email subject | +| ↳ `interested` | boolean | Whether the reply is marked interested | +| ↳ `automated_reply` | boolean | Whether the reply is automated | +| ↳ `html_body` | string | Reply HTML body | +| ↳ `text_body` | string | Reply plain text body | +| ↳ `raw_body` | string | Raw MIME reply body | +| ↳ `headers` | string | Encoded raw email headers | +| ↳ `date_received` | string | Reply received timestamp | +| ↳ `from_name` | string | Reply sender name | +| ↳ `from_email_address` | string | Reply sender email address | +| ↳ `primary_to_email_address` | string | Primary recipient email address | +| ↳ `to` | json | Reply To recipients | +| ↳ `cc` | json | Reply CC recipients | +| ↳ `bcc` | json | Reply BCC recipients | +| ↳ `parent_id` | number | Parent reply ID | +| ↳ `reply_type` | string | Reply type | +| ↳ `folder` | string | Reply folder | +| ↳ `raw_message_id` | string | Raw email message ID | +| ↳ `created_at` | string | Reply creation timestamp | +| ↳ `updated_at` | string | Reply update timestamp | +| ↳ `attachments` | json | Reply attachments | +| `campaignEvent` | object | campaignEvent output from the tool | +| ↳ `id` | number | Campaign event ID | +| ↳ `event_type` | string | Campaign event type | +| ↳ `created_at_local` | string | Campaign event local creation timestamp | +| ↳ `local_timezone` | string | Campaign event local timezone | +| ↳ `created_at` | string | Campaign event creation timestamp | +| `lead` | object | lead output from the tool | +| ↳ `id` | number | Lead ID | +| ↳ `email` | string | Lead email address | +| ↳ `first_name` | string | Lead first name | +| ↳ `last_name` | string | Lead last name | +| ↳ `status` | string | Lead status | +| ↳ `title` | string | Lead title | +| ↳ `company` | string | Lead company | +| ↳ `custom_variables` | json | Lead custom variables | +| ↳ `emails_sent` | number | Lead emails sent count | +| ↳ `opens` | number | Lead open count | +| ↳ `unique_opens` | number | Lead unique open count | +| ↳ `replies` | number | Lead reply count | +| ↳ `unique_replies` | number | Lead unique reply count | +| ↳ `bounces` | number | Lead bounce count | +| `campaign` | object | campaign output from the tool | +| ↳ `id` | number | Campaign ID | +| ↳ `name` | string | Campaign name | +| `scheduledEmail` | object | scheduledEmail output from the tool | +| ↳ `id` | number | Scheduled email ID | +| ↳ `sequence_step_id` | number | Sequence step ID | +| ↳ `sequence_step_order` | number | Sequence step order | +| ↳ `sequence_step_variant` | number | Sequence step variant | +| ↳ `status` | string | Scheduled email status | +| ↳ `scheduled_date_est` | string | Scheduled date in EST | +| ↳ `scheduled_date_local` | string | Scheduled date in local timezone | +| ↳ `local_timezone` | string | Scheduled email local timezone | +| ↳ `sent_at` | string | Email sent timestamp | +| ↳ `opens` | number | Open count | +| ↳ `replies` | number | Reply count | +| ↳ `unique_opens` | number | Unique open count | +| ↳ `unique_replies` | number | Unique reply count | +| ↳ `interested` | string | Interested status | +| ↳ `raw_message_id` | string | Raw email message ID | +| `senderEmail` | object | senderEmail output from the tool | +| ↳ `id` | number | Sender email ID | +| ↳ `name` | string | Sender email name | +| ↳ `email` | string | Sender email address | +| ↳ `status` | string | Sender email status | +| ↳ `account_type` | string | Sender email connection type | +| ↳ `daily_limit` | number | Sender email daily limit | +| ↳ `emails_sent` | number | Sender email sent count | +| ↳ `replied` | number | Sender email replied count | +| ↳ `opened` | number | Sender email opened count | +| ↳ `unsubscribed` | number | Sender email unsubscribed count | +| ↳ `bounced` | number | Sender email bounced count | +| ↳ `unique_replies` | number | Sender email unique reply count | +| ↳ `unique_opens` | number | Sender email unique open count | +| ↳ `total_leads_contacted` | number | Sender email total leads contacted | +| ↳ `interested` | number | Sender email interested count | +| ↳ `created_at` | string | Sender email creation timestamp | +| ↳ `updated_at` | string | Sender email update timestamp | + + +--- + +### Email Bison Contact Unsubscribed + +Trigger when a contact unsubscribes in Email Bison + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `scheduledEmail` | object | scheduledEmail output from the tool | +| ↳ `id` | number | Scheduled email ID | +| ↳ `lead_id` | number | Lead ID | +| ↳ `sequence_step_id` | number | Sequence step ID | +| ↳ `sequence_step_order` | number | Sequence step order | +| ↳ `sequence_step_variant` | number | Sequence step variant | +| ↳ `email_subject` | string | Email subject | +| ↳ `email_body` | string | Email body HTML | +| ↳ `status` | string | Scheduled email status | +| ↳ `scheduled_date_est` | string | Scheduled date in EST | +| ↳ `scheduled_date_local` | string | Scheduled date in local timezone | +| ↳ `local_timezone` | string | Scheduled email local timezone | +| ↳ `sent_at` | string | Email sent timestamp | +| ↳ `opens` | number | Open count | +| ↳ `replies` | number | Reply count | +| ↳ `unique_opens` | number | Unique open count | +| ↳ `unique_replies` | number | Unique reply count | +| ↳ `interested` | string | Interested status | +| ↳ `raw_message_id` | string | Raw email message ID | +| `campaignEvent` | object | campaignEvent output from the tool | +| ↳ `id` | number | Campaign event ID | +| ↳ `event_type` | string | Campaign event type | +| ↳ `created_at_local` | string | Campaign event local creation timestamp | +| ↳ `local_timezone` | string | Campaign event local timezone | +| ↳ `created_at` | string | Campaign event creation timestamp | +| `lead` | object | lead output from the tool | +| ↳ `id` | number | Lead ID | +| ↳ `email` | string | Lead email address | +| ↳ `first_name` | string | Lead first name | +| ↳ `last_name` | string | Lead last name | +| ↳ `status` | string | Lead status | +| ↳ `title` | string | Lead title | +| ↳ `company` | string | Lead company | +| ↳ `custom_variables` | json | Lead custom variables | +| ↳ `emails_sent` | number | Lead emails sent count | +| ↳ `opens` | number | Lead open count | +| ↳ `unique_opens` | number | Lead unique open count | +| ↳ `replies` | number | Lead reply count | +| ↳ `unique_replies` | number | Lead unique reply count | +| ↳ `bounces` | number | Lead bounce count | +| `campaign` | object | campaign output from the tool | +| ↳ `id` | number | Campaign ID | +| ↳ `name` | string | Campaign name | +| `senderEmail` | object | senderEmail output from the tool | +| ↳ `id` | number | Sender email ID | +| ↳ `name` | string | Sender email name | +| ↳ `email` | string | Sender email address | +| ↳ `status` | string | Sender email status | +| ↳ `account_type` | string | Sender email connection type | +| ↳ `daily_limit` | number | Sender email daily limit | +| ↳ `emails_sent` | number | Sender email sent count | +| ↳ `replied` | number | Sender email replied count | +| ↳ `opened` | number | Sender email opened count | +| ↳ `unsubscribed` | number | Sender email unsubscribed count | +| ↳ `bounced` | number | Sender email bounced count | +| ↳ `unique_replies` | number | Sender email unique reply count | +| ↳ `unique_opens` | number | Sender email unique open count | +| ↳ `total_leads_contacted` | number | Sender email total leads contacted | +| ↳ `interested` | number | Sender email interested count | +| ↳ `created_at` | string | Sender email creation timestamp | +| ↳ `updated_at` | string | Sender email update timestamp | + + +--- + +### Email Bison Email Account Added + +Trigger when a sender email account is added to Email Bison + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `senderEmail` | object | senderEmail output from the tool | +| ↳ `id` | number | Sender email ID | +| ↳ `name` | string | Sender email name | +| ↳ `email` | string | Sender email address | +| ↳ `status` | string | Sender email status | +| ↳ `account_type` | string | Sender email connection type | +| ↳ `daily_limit` | number | Sender email daily limit | +| ↳ `emails_sent` | number | Sender email sent count | +| ↳ `replied` | number | Sender email replied count | +| ↳ `opened` | number | Sender email opened count | +| ↳ `unsubscribed` | number | Sender email unsubscribed count | +| ↳ `bounced` | number | Sender email bounced count | +| ↳ `unique_replies` | number | Sender email unique reply count | +| ↳ `unique_opens` | number | Sender email unique open count | +| ↳ `total_leads_contacted` | number | Sender email total leads contacted | +| ↳ `interested` | number | Sender email interested count | +| ↳ `created_at` | string | Sender email creation timestamp | +| ↳ `updated_at` | string | Sender email update timestamp | + + +--- + +### Email Bison Email Account Disconnected + +Trigger when a sender email account disconnects in Email Bison + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `senderEmail` | object | senderEmail output from the tool | +| ↳ `id` | number | Sender email ID | +| ↳ `name` | string | Sender email name | +| ↳ `email` | string | Sender email address | +| ↳ `status` | string | Sender email status | +| ↳ `account_type` | string | Sender email connection type | +| ↳ `daily_limit` | number | Sender email daily limit | +| ↳ `emails_sent` | number | Sender email sent count | +| ↳ `replied` | number | Sender email replied count | +| ↳ `opened` | number | Sender email opened count | +| ↳ `unsubscribed` | number | Sender email unsubscribed count | +| ↳ `bounced` | number | Sender email bounced count | +| ↳ `unique_replies` | number | Sender email unique reply count | +| ↳ `unique_opens` | number | Sender email unique open count | +| ↳ `total_leads_contacted` | number | Sender email total leads contacted | +| ↳ `interested` | number | Sender email interested count | +| ↳ `created_at` | string | Sender email creation timestamp | +| ↳ `updated_at` | string | Sender email update timestamp | + + +--- + +### Email Bison Email Account Reconnected + +Trigger when a sender email account reconnects in Email Bison + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `senderEmail` | object | senderEmail output from the tool | +| ↳ `id` | number | Sender email ID | +| ↳ `name` | string | Sender email name | +| ↳ `email` | string | Sender email address | +| ↳ `status` | string | Sender email status | +| ↳ `account_type` | string | Sender email connection type | +| ↳ `daily_limit` | number | Sender email daily limit | +| ↳ `emails_sent` | number | Sender email sent count | +| ↳ `replied` | number | Sender email replied count | +| ↳ `opened` | number | Sender email opened count | +| ↳ `unsubscribed` | number | Sender email unsubscribed count | +| ↳ `bounced` | number | Sender email bounced count | +| ↳ `unique_replies` | number | Sender email unique reply count | +| ↳ `unique_opens` | number | Sender email unique open count | +| ↳ `total_leads_contacted` | number | Sender email total leads contacted | +| ↳ `interested` | number | Sender email interested count | +| ↳ `created_at` | string | Sender email creation timestamp | +| ↳ `updated_at` | string | Sender email update timestamp | + + +--- + +### Email Bison Email Account Removed + +Trigger when a sender email account is removed from Email Bison + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `senderEmail` | object | senderEmail output from the tool | +| ↳ `id` | number | Sender email ID | +| ↳ `name` | string | Sender email name | +| ↳ `email` | string | Sender email address | +| ↳ `status` | string | Sender email status | +| ↳ `account_type` | string | Sender email connection type | +| ↳ `daily_limit` | number | Sender email daily limit | +| ↳ `emails_sent` | number | Sender email sent count | +| ↳ `replied` | number | Sender email replied count | +| ↳ `opened` | number | Sender email opened count | +| ↳ `unsubscribed` | number | Sender email unsubscribed count | +| ↳ `bounced` | number | Sender email bounced count | +| ↳ `unique_replies` | number | Sender email unique reply count | +| ↳ `unique_opens` | number | Sender email unique open count | +| ↳ `total_leads_contacted` | number | Sender email total leads contacted | +| ↳ `interested` | number | Sender email interested count | +| ↳ `created_at` | string | Sender email creation timestamp | +| ↳ `updated_at` | string | Sender email update timestamp | + + +--- + +### Email Bison Email Bounced + +Trigger when an Email Bison campaign email bounces + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `reply` | object | reply output from the tool | +| ↳ `id` | number | Reply ID | +| ↳ `uuid` | string | Reply UUID | +| ↳ `email_subject` | string | Reply email subject | +| ↳ `interested` | boolean | Whether the reply is marked interested | +| ↳ `automated_reply` | boolean | Whether the reply is automated | +| ↳ `html_body` | string | Reply HTML body | +| ↳ `text_body` | string | Reply plain text body | +| ↳ `raw_body` | string | Raw MIME reply body | +| ↳ `headers` | string | Encoded raw email headers | +| ↳ `date_received` | string | Reply received timestamp | +| ↳ `from_name` | string | Reply sender name | +| ↳ `from_email_address` | string | Reply sender email address | +| ↳ `primary_to_email_address` | string | Primary recipient email address | +| ↳ `to` | json | Reply To recipients | +| ↳ `cc` | json | Reply CC recipients | +| ↳ `bcc` | json | Reply BCC recipients | +| ↳ `parent_id` | number | Parent reply ID | +| ↳ `reply_type` | string | Reply type | +| ↳ `folder` | string | Reply folder | +| ↳ `raw_message_id` | string | Raw email message ID | +| ↳ `created_at` | string | Reply creation timestamp | +| ↳ `updated_at` | string | Reply update timestamp | +| ↳ `attachments` | json | Reply attachments | +| `campaignEvent` | object | campaignEvent output from the tool | +| ↳ `id` | number | Campaign event ID | +| ↳ `event_type` | string | Campaign event type | +| ↳ `created_at_local` | string | Campaign event local creation timestamp | +| ↳ `local_timezone` | string | Campaign event local timezone | +| ↳ `created_at` | string | Campaign event creation timestamp | +| `lead` | object | lead output from the tool | +| ↳ `id` | number | Lead ID | +| ↳ `email` | string | Lead email address | +| ↳ `first_name` | string | Lead first name | +| ↳ `last_name` | string | Lead last name | +| ↳ `status` | string | Lead status | +| ↳ `title` | string | Lead title | +| ↳ `company` | string | Lead company | +| ↳ `custom_variables` | json | Lead custom variables | +| ↳ `emails_sent` | number | Lead emails sent count | +| ↳ `opens` | number | Lead open count | +| ↳ `unique_opens` | number | Lead unique open count | +| ↳ `replies` | number | Lead reply count | +| ↳ `unique_replies` | number | Lead unique reply count | +| ↳ `bounces` | number | Lead bounce count | +| `campaign` | object | campaign output from the tool | +| ↳ `id` | number | Campaign ID | +| ↳ `name` | string | Campaign name | +| `scheduledEmail` | object | scheduledEmail output from the tool | +| ↳ `id` | number | Scheduled email ID | +| ↳ `sequence_step_id` | number | Sequence step ID | +| ↳ `sequence_step_order` | number | Sequence step order | +| ↳ `sequence_step_variant` | number | Sequence step variant | +| ↳ `status` | string | Scheduled email status | +| ↳ `scheduled_date_est` | string | Scheduled date in EST | +| ↳ `scheduled_date_local` | string | Scheduled date in local timezone | +| ↳ `local_timezone` | string | Scheduled email local timezone | +| ↳ `sent_at` | string | Email sent timestamp | +| ↳ `opens` | number | Open count | +| ↳ `replies` | number | Reply count | +| ↳ `unique_opens` | number | Unique open count | +| ↳ `unique_replies` | number | Unique reply count | +| ↳ `interested` | string | Interested status | +| ↳ `raw_message_id` | string | Raw email message ID | +| `senderEmail` | object | senderEmail output from the tool | +| ↳ `id` | number | Sender email ID | +| ↳ `name` | string | Sender email name | +| ↳ `email` | string | Sender email address | +| ↳ `status` | string | Sender email status | +| ↳ `account_type` | string | Sender email connection type | +| ↳ `daily_limit` | number | Sender email daily limit | +| ↳ `emails_sent` | number | Sender email sent count | +| ↳ `replied` | number | Sender email replied count | +| ↳ `opened` | number | Sender email opened count | +| ↳ `unsubscribed` | number | Sender email unsubscribed count | +| ↳ `bounced` | number | Sender email bounced count | +| ↳ `unique_replies` | number | Sender email unique reply count | +| ↳ `unique_opens` | number | Sender email unique open count | +| ↳ `total_leads_contacted` | number | Sender email total leads contacted | +| ↳ `interested` | number | Sender email interested count | +| ↳ `created_at` | string | Sender email creation timestamp | +| ↳ `updated_at` | string | Sender email update timestamp | + + +--- + +### Email Bison Email Opened + +Trigger when an Email Bison campaign email is opened + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `scheduledEmail` | object | scheduledEmail output from the tool | +| ↳ `id` | number | Scheduled email ID | +| ↳ `lead_id` | number | Lead ID | +| ↳ `sequence_step_id` | number | Sequence step ID | +| ↳ `sequence_step_order` | number | Sequence step order | +| ↳ `sequence_step_variant` | number | Sequence step variant | +| ↳ `email_subject` | string | Email subject | +| ↳ `email_body` | string | Email body HTML | +| ↳ `status` | string | Scheduled email status | +| ↳ `scheduled_date_est` | string | Scheduled date in EST | +| ↳ `scheduled_date_local` | string | Scheduled date in local timezone | +| ↳ `local_timezone` | string | Scheduled email local timezone | +| ↳ `sent_at` | string | Email sent timestamp | +| ↳ `opens` | number | Open count | +| ↳ `replies` | number | Reply count | +| ↳ `unique_opens` | number | Unique open count | +| ↳ `unique_replies` | number | Unique reply count | +| ↳ `interested` | string | Interested status | +| ↳ `raw_message_id` | string | Raw email message ID | +| `campaignEvent` | object | campaignEvent output from the tool | +| ↳ `id` | number | Campaign event ID | +| ↳ `event_type` | string | Campaign event type | +| ↳ `created_at_local` | string | Campaign event local creation timestamp | +| ↳ `local_timezone` | string | Campaign event local timezone | +| ↳ `created_at` | string | Campaign event creation timestamp | +| `lead` | object | lead output from the tool | +| ↳ `id` | number | Lead ID | +| ↳ `email` | string | Lead email address | +| ↳ `first_name` | string | Lead first name | +| ↳ `last_name` | string | Lead last name | +| ↳ `status` | string | Lead status | +| ↳ `title` | string | Lead title | +| ↳ `company` | string | Lead company | +| ↳ `custom_variables` | json | Lead custom variables | +| ↳ `emails_sent` | number | Lead emails sent count | +| ↳ `opens` | number | Lead open count | +| ↳ `unique_opens` | number | Lead unique open count | +| ↳ `replies` | number | Lead reply count | +| ↳ `unique_replies` | number | Lead unique reply count | +| ↳ `bounces` | number | Lead bounce count | +| `campaign` | object | campaign output from the tool | +| ↳ `id` | number | Campaign ID | +| ↳ `name` | string | Campaign name | +| `senderEmail` | object | senderEmail output from the tool | +| ↳ `id` | number | Sender email ID | +| ↳ `name` | string | Sender email name | +| ↳ `email` | string | Sender email address | +| ↳ `status` | string | Sender email status | +| ↳ `account_type` | string | Sender email connection type | +| ↳ `daily_limit` | number | Sender email daily limit | +| ↳ `emails_sent` | number | Sender email sent count | +| ↳ `replied` | number | Sender email replied count | +| ↳ `opened` | number | Sender email opened count | +| ↳ `unsubscribed` | number | Sender email unsubscribed count | +| ↳ `bounced` | number | Sender email bounced count | +| ↳ `unique_replies` | number | Sender email unique reply count | +| ↳ `unique_opens` | number | Sender email unique open count | +| ↳ `total_leads_contacted` | number | Sender email total leads contacted | +| ↳ `interested` | number | Sender email interested count | +| ↳ `created_at` | string | Sender email creation timestamp | +| ↳ `updated_at` | string | Sender email update timestamp | + + +--- + +### Email Bison Email Sent + +Trigger when a campaign email is sent in Email Bison + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `scheduledEmail` | object | scheduledEmail output from the tool | +| ↳ `id` | number | Scheduled email ID | +| ↳ `lead_id` | number | Lead ID | +| ↳ `sequence_step_id` | number | Sequence step ID | +| ↳ `sequence_step_order` | number | Sequence step order | +| ↳ `sequence_step_variant` | number | Sequence step variant | +| ↳ `email_subject` | string | Email subject | +| ↳ `email_body` | string | Email body HTML | +| ↳ `status` | string | Scheduled email status | +| ↳ `scheduled_date_est` | string | Scheduled date in EST | +| ↳ `scheduled_date_local` | string | Scheduled date in local timezone | +| ↳ `local_timezone` | string | Scheduled email local timezone | +| ↳ `sent_at` | string | Email sent timestamp | +| ↳ `opens` | number | Open count | +| ↳ `replies` | number | Reply count | +| ↳ `unique_opens` | number | Unique open count | +| ↳ `unique_replies` | number | Unique reply count | +| ↳ `interested` | string | Interested status | +| ↳ `raw_message_id` | string | Raw email message ID | +| `campaignEvent` | object | campaignEvent output from the tool | +| ↳ `id` | number | Campaign event ID | +| ↳ `event_type` | string | Campaign event type | +| ↳ `created_at_local` | string | Campaign event local creation timestamp | +| ↳ `local_timezone` | string | Campaign event local timezone | +| ↳ `created_at` | string | Campaign event creation timestamp | +| `lead` | object | lead output from the tool | +| ↳ `id` | number | Lead ID | +| ↳ `email` | string | Lead email address | +| ↳ `first_name` | string | Lead first name | +| ↳ `last_name` | string | Lead last name | +| ↳ `status` | string | Lead status | +| ↳ `title` | string | Lead title | +| ↳ `company` | string | Lead company | +| ↳ `custom_variables` | json | Lead custom variables | +| ↳ `emails_sent` | number | Lead emails sent count | +| ↳ `opens` | number | Lead open count | +| ↳ `unique_opens` | number | Lead unique open count | +| ↳ `replies` | number | Lead reply count | +| ↳ `unique_replies` | number | Lead unique reply count | +| ↳ `bounces` | number | Lead bounce count | +| `campaign` | object | campaign output from the tool | +| ↳ `id` | number | Campaign ID | +| ↳ `name` | string | Campaign name | +| `senderEmail` | object | senderEmail output from the tool | +| ↳ `id` | number | Sender email ID | +| ↳ `name` | string | Sender email name | +| ↳ `email` | string | Sender email address | +| ↳ `status` | string | Sender email status | +| ↳ `account_type` | string | Sender email connection type | +| ↳ `daily_limit` | number | Sender email daily limit | +| ↳ `emails_sent` | number | Sender email sent count | +| ↳ `replied` | number | Sender email replied count | +| ↳ `opened` | number | Sender email opened count | +| ↳ `unsubscribed` | number | Sender email unsubscribed count | +| ↳ `bounced` | number | Sender email bounced count | +| ↳ `unique_replies` | number | Sender email unique reply count | +| ↳ `unique_opens` | number | Sender email unique open count | +| ↳ `total_leads_contacted` | number | Sender email total leads contacted | +| ↳ `interested` | number | Sender email interested count | +| ↳ `created_at` | string | Sender email creation timestamp | +| ↳ `updated_at` | string | Sender email update timestamp | + + +--- + +### Email Bison Manual Email Sent + +Trigger when a manual email is sent in Email Bison + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `reply` | object | reply output from the tool | +| ↳ `id` | number | Reply ID | +| ↳ `email_subject` | string | Reply email subject | +| ↳ `interested` | boolean | Whether the reply is marked interested | +| ↳ `automated_reply` | boolean | Whether the reply is automated | +| ↳ `html_body` | string | Reply HTML body | +| ↳ `text_body` | string | Reply plain text body | +| ↳ `raw_body` | string | Raw MIME reply body | +| ↳ `headers` | string | Encoded raw email headers | +| ↳ `date_received` | string | Reply received timestamp | +| ↳ `reply_type` | string | Reply type | +| ↳ `from_name` | string | Reply sender name | +| ↳ `from_email_address` | string | Reply sender email address | +| ↳ `primary_to_email_address` | string | Primary recipient email address | +| ↳ `to` | json | Reply To recipients | +| ↳ `cc` | json | Reply CC recipients | +| ↳ `bcc` | json | Reply BCC recipients | +| ↳ `parent_id` | json | Parent reply ID | +| ↳ `folder` | string | Reply folder | +| ↳ `raw_message_id` | string | Raw email message ID | +| ↳ `created_at` | string | Reply creation timestamp | +| ↳ `updated_at` | string | Reply update timestamp | +| ↳ `attachments` | json | Reply attachments | +| `lead` | object | lead output from the tool | +| ↳ `id` | number | Lead ID | +| ↳ `email` | string | Lead email address | +| ↳ `first_name` | string | Lead first name | +| ↳ `last_name` | string | Lead last name | +| ↳ `status` | string | Lead status | +| ↳ `title` | string | Lead title | +| ↳ `company` | string | Lead company | +| ↳ `custom_variables` | json | Lead custom variables | +| ↳ `emails_sent` | number | Lead emails sent count | +| ↳ `opens` | number | Lead open count | +| ↳ `unique_opens` | number | Lead unique open count | +| ↳ `replies` | number | Lead reply count | +| ↳ `unique_replies` | number | Lead unique reply count | +| ↳ `bounces` | number | Lead bounce count | +| `campaign` | object | campaign output from the tool | +| ↳ `id` | number | Campaign ID | +| ↳ `name` | string | Campaign name | +| `scheduledEmail` | object | scheduledEmail output from the tool | +| ↳ `id` | number | Scheduled email ID | +| ↳ `sequence_step_id` | number | Sequence step ID | +| ↳ `sequence_step_order` | number | Sequence step order | +| ↳ `sequence_step_variant` | number | Sequence step variant | +| ↳ `status` | string | Scheduled email status | +| ↳ `scheduled_date_est` | string | Scheduled date in EST | +| ↳ `scheduled_date_local` | string | Scheduled date in local timezone | +| ↳ `local_timezone` | string | Scheduled email local timezone | +| ↳ `sent_at` | string | Email sent timestamp | +| ↳ `opens` | number | Open count | +| ↳ `replies` | number | Reply count | +| ↳ `unique_opens` | number | Unique open count | +| ↳ `unique_replies` | number | Unique reply count | +| ↳ `interested` | json | Interested status | +| ↳ `raw_message_id` | string | Raw email message ID | +| `senderEmail` | object | senderEmail output from the tool | +| ↳ `id` | number | Sender email ID | +| ↳ `name` | string | Sender email name | +| ↳ `email` | string | Sender email address | +| ↳ `status` | string | Sender email status | +| ↳ `account_type` | string | Sender email connection type | +| ↳ `daily_limit` | number | Sender email daily limit | +| ↳ `emails_sent` | number | Sender email sent count | +| ↳ `replied` | number | Sender email replied count | +| ↳ `opened` | number | Sender email opened count | +| ↳ `unsubscribed` | number | Sender email unsubscribed count | +| ↳ `bounced` | number | Sender email bounced count | +| ↳ `unique_replies` | number | Sender email unique reply count | +| ↳ `unique_opens` | number | Sender email unique open count | +| ↳ `total_leads_contacted` | number | Sender email total leads contacted | +| ↳ `interested` | number | Sender email interested count | +| ↳ `created_at` | string | Sender email creation timestamp | +| ↳ `updated_at` | string | Sender email update timestamp | + + +--- + +### Email Bison Tag Attached + +Trigger when a custom tag is attached to a taggable in Email Bison + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `tagId` | number | Email Bison tag ID | +| `tagName` | string | Email Bison tag name | +| `taggableId` | number | ID of the tagged resource | +| `taggableType` | string | Type of the tagged resource | + + +--- + +### Email Bison Tag Removed + +Trigger when a custom tag is removed from a taggable in Email Bison + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `tagId` | number | Email Bison tag ID | +| `tagName` | string | Email Bison tag name | +| `taggableId` | number | ID of the tagged resource | +| `taggableType` | string | Type of the tagged resource | + + +--- + +### Email Bison Untracked Reply Received + +Trigger when Email Bison receives a reply not tied to a scheduled campaign email + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `reply` | object | reply output from the tool | +| ↳ `id` | number | Reply ID | +| ↳ `uuid` | string | Reply UUID | +| ↳ `email_subject` | string | Reply email subject | +| ↳ `interested` | boolean | Whether the reply is marked interested | +| ↳ `automated_reply` | boolean | Whether the reply is automated | +| ↳ `html_body` | string | Reply HTML body | +| ↳ `text_body` | string | Reply plain text body | +| ↳ `raw_body` | string | Raw MIME reply body | +| ↳ `headers` | string | Encoded raw email headers | +| ↳ `date_received` | string | Reply received timestamp | +| ↳ `from_name` | string | Reply sender name | +| ↳ `from_email_address` | string | Reply sender email address | +| ↳ `primary_to_email_address` | string | Primary recipient email address | +| ↳ `to` | json | Reply To recipients | +| ↳ `cc` | json | Reply CC recipients | +| ↳ `bcc` | json | Reply BCC recipients | +| ↳ `parent_id` | number | Parent reply ID | +| ↳ `reply_type` | string | Reply type | +| ↳ `folder` | string | Reply folder | +| ↳ `raw_message_id` | string | Raw email message ID | +| ↳ `created_at` | string | Reply creation timestamp | +| ↳ `updated_at` | string | Reply update timestamp | +| ↳ `attachments` | json | Reply attachments | +| `senderEmail` | object | senderEmail output from the tool | +| ↳ `id` | number | Sender email ID | +| ↳ `name` | string | Sender email name | +| ↳ `email` | string | Sender email address | +| ↳ `status` | string | Sender email status | +| ↳ `account_type` | string | Sender email connection type | +| ↳ `daily_limit` | number | Sender email daily limit | +| ↳ `emails_sent` | number | Sender email sent count | +| ↳ `replied` | number | Sender email replied count | +| ↳ `opened` | number | Sender email opened count | +| ↳ `unsubscribed` | number | Sender email unsubscribed count | +| ↳ `bounced` | number | Sender email bounced count | +| ↳ `unique_replies` | number | Sender email unique reply count | +| ↳ `unique_opens` | number | Sender email unique open count | +| ↳ `total_leads_contacted` | number | Sender email total leads contacted | +| ↳ `interested` | number | Sender email interested count | +| ↳ `created_at` | string | Sender email creation timestamp | +| ↳ `updated_at` | string | Sender email update timestamp | + + +--- + +### Email Bison Warmup Disabled Causing Bounces + +Trigger when warmup is disabled for a sender email causing too many bounces + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `senderEmail` | object | senderEmail output from the tool | +| ↳ `id` | number | Sender email ID | +| ↳ `name` | string | Sender email name | +| ↳ `email` | string | Sender email address | +| ↳ `status` | string | Sender email status | +| ↳ `account_type` | string | Sender email connection type | +| ↳ `daily_limit` | number | Sender email daily limit | +| ↳ `emails_sent` | number | Sender email sent count | +| ↳ `replied` | number | Sender email replied count | +| ↳ `opened` | number | Sender email opened count | +| ↳ `unsubscribed` | number | Sender email unsubscribed count | +| ↳ `bounced` | number | Sender email bounced count | +| ↳ `unique_replies` | number | Sender email unique reply count | +| ↳ `unique_opens` | number | Sender email unique open count | +| ↳ `total_leads_contacted` | number | Sender email total leads contacted | +| ↳ `interested` | number | Sender email interested count | +| ↳ `created_at` | string | Sender email creation timestamp | +| ↳ `updated_at` | string | Sender email update timestamp | + + +--- + +### Email Bison Warmup Disabled Receiving Bounces + +Trigger when warmup is disabled for a sender email receiving too many bounces + +#### Configuration + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | API Key | +| `apiBaseUrl` | string | Yes | Instance URL | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `eventType` | string | Email Bison webhook event type | +| `eventName` | string | Human-readable Email Bison event name | +| `instanceUrl` | string | Email Bison instance URL | +| `workspaceId` | number | Email Bison workspace ID | +| `workspaceName` | string | Email Bison workspace name | +| `event` | json | Raw Email Bison event metadata object | +| `data` | json | Raw Email Bison event data object | +| `senderEmail` | object | senderEmail output from the tool | +| ↳ `id` | number | Sender email ID | +| ↳ `name` | string | Sender email name | +| ↳ `email` | string | Sender email address | +| ↳ `status` | string | Sender email status | +| ↳ `account_type` | string | Sender email connection type | +| ↳ `daily_limit` | number | Sender email daily limit | +| ↳ `emails_sent` | number | Sender email sent count | +| ↳ `replied` | number | Sender email replied count | +| ↳ `opened` | number | Sender email opened count | +| ↳ `unsubscribed` | number | Sender email unsubscribed count | +| ↳ `bounced` | number | Sender email bounced count | +| ↳ `unique_replies` | number | Sender email unique reply count | +| ↳ `unique_opens` | number | Sender email unique open count | +| ↳ `total_leads_contacted` | number | Sender email total leads contacted | +| ↳ `interested` | number | Sender email interested count | +| ↳ `created_at` | string | Sender email creation timestamp | +| ↳ `updated_at` | string | Sender email update timestamp | + diff --git a/apps/docs/content/docs/en/triggers/meta.json b/apps/docs/content/docs/en/triggers/meta.json index 7eff814aad7..70d13afd920 100644 --- a/apps/docs/content/docs/en/triggers/meta.json +++ b/apps/docs/content/docs/en/triggers/meta.json @@ -12,6 +12,7 @@ "calendly", "circleback", "confluence", + "emailbison", "fathom", "fireflies", "github", diff --git a/apps/docs/package.json b/apps/docs/package.json index d9d9a53f519..d614fe60b0a 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -26,7 +26,7 @@ "fumadocs-openapi": "10.8.1", "fumadocs-ui": "16.8.5", "lucide-react": "^0.511.0", - "next": "16.1.6", + "next": "16.2.4", "next-themes": "^0.4.6", "postgres": "^3.4.5", "react": "19.2.4", diff --git a/apps/sim/app/(auth)/oauth/consent/page.tsx b/apps/sim/app/(auth)/oauth/consent/page.tsx index 8addf4e82b4..ea624d9b87f 100644 --- a/apps/sim/app/(auth)/oauth/consent/page.tsx +++ b/apps/sim/app/(auth)/oauth/consent/page.tsx @@ -1,6 +1,6 @@ 'use client' -import { useCallback, useEffect, useState } from 'react' +import { Suspense, useCallback, useEffect, useState } from 'react' import { ArrowLeftRight } from 'lucide-react' import Image from 'next/image' import { useRouter, useSearchParams } from 'next/navigation' @@ -25,6 +25,14 @@ interface ClientInfo { } export default function OAuthConsentPage() { + return ( + + + + ) +} + +function OAuthConsentInner() { const router = useRouter() const searchParams = useSearchParams() const { data: session } = useSession() diff --git a/apps/sim/app/(auth)/reset-password/page.tsx b/apps/sim/app/(auth)/reset-password/page.tsx index cb6470bba0e..8d9ee5dfd42 100644 --- a/apps/sim/app/(auth)/reset-password/page.tsx +++ b/apps/sim/app/(auth)/reset-password/page.tsx @@ -5,4 +5,6 @@ export const metadata: Metadata = { title: 'Reset Password', } +export const dynamic = 'force-dynamic' + export default ResetPasswordPage diff --git a/apps/sim/app/(landing)/components/navbar/navbar.tsx b/apps/sim/app/(landing)/components/navbar/navbar.tsx index 4ee2fdb2aca..e5503dcb27e 100644 --- a/apps/sim/app/(landing)/components/navbar/navbar.tsx +++ b/apps/sim/app/(landing)/components/navbar/navbar.tsx @@ -4,7 +4,6 @@ import { useCallback, useContext, useEffect, useRef, useState, useSyncExternalSt import dynamic from 'next/dynamic' import Image from 'next/image' import Link from 'next/link' -import { useSearchParams } from 'next/navigation' import { GithubOutlineIcon } from '@/components/icons' import { cn } from '@/lib/core/utils/cn' import { SessionContext } from '@/app/_shell/providers/session-provider' @@ -52,12 +51,14 @@ interface NavbarProps { export default function Navbar({ logoOnly = false, blogPosts = [] }: NavbarProps) { const brand = getBrandConfig() - const searchParams = useSearchParams() const sessionCtx = useContext(SessionContext) const session = sessionCtx?.data ?? null const isSessionPending = sessionCtx?.isPending ?? true const isAuthenticated = Boolean(session?.user?.id) - const isBrowsingHome = searchParams.has('home') + const [isBrowsingHome, setIsBrowsingHome] = useState(false) + useEffect(() => { + setIsBrowsingHome(new URLSearchParams(window.location.search).has('home')) + }, []) const useHomeLinks = isAuthenticated || isBrowsingHome const logoHref = useHomeLinks ? '/?home' : '/' const mounted = useSyncExternalStore( diff --git a/apps/sim/app/(landing)/integrations/(shell)/[slug]/page.tsx b/apps/sim/app/(landing)/integrations/(shell)/[slug]/page.tsx index 51a6dfa72fe..eb700c9708b 100644 --- a/apps/sim/app/(landing)/integrations/(shell)/[slug]/page.tsx +++ b/apps/sim/app/(landing)/integrations/(shell)/[slug]/page.tsx @@ -75,12 +75,21 @@ const AUTH_STEP: Record = { none: 'Authenticate your account to connect.', } +/** + * Ensures autogenerated prose can be safely composed with a following sentence. + */ +function sentenceWithTerminalPunctuation(value: string): string { + const trimmedValue = value.trim() + return /[.!?]$/.test(trimmedValue) ? trimmedValue : `${trimmedValue}.` +} + /** * Generates targeted FAQs from integration metadata. * Questions mirror real search queries to drive FAQPage rich snippets. */ function buildFAQs(integration: Integration): FAQItem[] { const { name, description, operations, triggers, authType } = integration + const faqDescription = sentenceWithTerminalPunctuation(description) const topOps = operations.slice(0, 5) const topOpNames = topOps.map((o) => o.name) const authStep = AUTH_STEP[authType] @@ -88,7 +97,7 @@ function buildFAQs(integration: Integration): FAQItem[] { const faqs: FAQItem[] = [ { question: `What is Sim's ${name} integration?`, - answer: `Sim's ${name} integration lets you build AI agents that automate tasks in ${name} without writing code. ${description} You can connect ${name} to hundreds of other services in the same agent — from CRMs and spreadsheets to messaging tools and databases.`, + answer: `Sim's ${name} integration lets you build AI agents that automate tasks in ${name} without writing code. ${faqDescription} You can connect ${name} to hundreds of other services in the same agent — from CRMs and spreadsheets to messaging tools and databases.`, }, { question: `What can I automate with ${name} in Sim?`, diff --git a/apps/sim/app/(landing)/integrations/data/icon-mapping.ts b/apps/sim/app/(landing)/integrations/data/icon-mapping.ts index 9d3280bd825..e19d3d267b1 100644 --- a/apps/sim/app/(landing)/integrations/data/icon-mapping.ts +++ b/apps/sim/app/(landing)/integrations/data/icon-mapping.ts @@ -51,6 +51,7 @@ import { DynamoDBIcon, ElasticsearchIcon, ElevenLabsIcon, + EmailBisonIcon, EnrichSoIcon, EvernoteIcon, ExaAIIcon, @@ -154,6 +155,7 @@ import { RootlyIcon, S3Icon, SalesforceIcon, + SapConcurIcon, SapS4HanaIcon, SESIcon, SearchIcon, @@ -248,6 +250,7 @@ export const blockTypeToIconMap: Record = { dynamodb: DynamoDBIcon, elasticsearch: ElasticsearchIcon, elevenlabs: ElevenLabsIcon, + emailbison: EmailBisonIcon, enrich: EnrichSoIcon, evernote: EvernoteIcon, exa: ExaAIIcon, @@ -352,6 +355,7 @@ export const blockTypeToIconMap: Record = { rootly: RootlyIcon, s3: S3Icon, salesforce: SalesforceIcon, + sap_concur: SapConcurIcon, sap_s4hana: SapS4HanaIcon, search: SearchIcon, secrets_manager: SecretsManagerIcon, @@ -361,7 +365,7 @@ export const blockTypeToIconMap: Record = { servicenow: ServiceNowIcon, ses: SESIcon, sftp: SftpIcon, - sharepoint: MicrosoftSharepointIcon, + sharepoint_v2: MicrosoftSharepointIcon, shopify: ShopifyIcon, similarweb: SimilarwebIcon, sixtyfour: SixtyfourIcon, diff --git a/apps/sim/app/(landing)/integrations/data/integrations.json b/apps/sim/app/(landing)/integrations/data/integrations.json index 346cb98feeb..9a1513dc6f8 100644 --- a/apps/sim/app/(landing)/integrations/data/integrations.json +++ b/apps/sim/app/(landing)/integrations/data/integrations.json @@ -321,7 +321,7 @@ "name": "Agiloft", "description": "Manage records in Agiloft CLM", "longDescription": "Integrate with Agiloft contract lifecycle management to create, read, update, delete, and search records. Supports file attachments, SQL-based selection, saved searches, and record locking across any table in your knowledge base.", - "bgColor": "#FFFFFF", + "bgColor": "#001028", "iconName": "AgiloftIcon", "docsUrl": "https://docs.sim.ai/tools/agiloft", "operations": [ @@ -372,9 +372,13 @@ { "name": "Lock Record", "description": "Lock, unlock, or check the lock status of an Agiloft record." + }, + { + "name": "Get Choice Line ID", + "description": "Resolve the internal numeric ID of a choice-list value, for use in EWSelect WHERE clauses against choice fields." } ], - "operationCount": 12, + "operationCount": 13, "triggers": [], "triggerCount": 0, "authType": "none", @@ -3633,6 +3637,77 @@ "integrationTypes": ["ai"], "tags": ["text-to-speech"] }, + { + "type": "emailbison", + "slug": "email-bison", + "name": "Email Bison", + "description": "Manage Email Bison leads, campaigns, replies, and tags", + "longDescription": "Integrate Email Bison into workflows. Create and update leads, manage campaigns, attach leads to campaigns, list replies, and organize leads with tags.", + "bgColor": "#FB7A22", + "iconName": "EmailBisonIcon", + "docsUrl": "https://docs.sim.ai/tools/emailbison", + "operations": [ + { + "name": "List Leads", + "description": "Retrieves leads from Email Bison with optional search and tag filters." + }, + { + "name": "Get Lead", + "description": "Retrieves a lead by Email Bison lead ID or email address." + }, + { + "name": "Create Lead", + "description": "Creates a single lead in Email Bison." + }, + { + "name": "Update Lead", + "description": "Updates an existing Email Bison lead. Fields omitted from a PUT update may be cleared by Email Bison." + }, + { + "name": "List Campaigns", + "description": "Retrieves Email Bison campaigns." + }, + { + "name": "Create Campaign", + "description": "Creates a new Email Bison campaign." + }, + { + "name": "Update Campaign", + "description": "Updates Email Bison campaign settings." + }, + { + "name": "Update Campaign Status", + "description": "Pauses, resumes, or archives an Email Bison campaign." + }, + { + "name": "Attach Leads to Campaign", + "description": "Adds existing Email Bison leads to a campaign." + }, + { + "name": "List Replies", + "description": "Retrieves Email Bison replies with optional status, folder, campaign, sender, lead, and tag filters." + }, + { + "name": "List Tags", + "description": "Retrieves all Email Bison tags for the authenticated workspace." + }, + { + "name": "Create Tag", + "description": "Creates a new Email Bison tag." + }, + { + "name": "Attach Tags to Leads", + "description": "Attaches Email Bison tags to one or more leads." + } + ], + "operationCount": 13, + "triggers": [], + "triggerCount": 0, + "authType": "api-key", + "category": "tools", + "integrationTypes": ["email", "developer-tools", "sales"], + "tags": ["sales-engagement", "email-marketing", "automation"] + }, { "type": "openai", "slug": "embeddings", @@ -11407,6 +11482,305 @@ "integrationTypes": ["crm", "customer-support", "sales"], "tags": ["sales-engagement", "customer-support"] }, + { + "type": "sap_concur", + "slug": "sap-concur", + "name": "SAP Concur", + "description": "Manage expense reports, travel requests, cash advances, and more in SAP Concur", + "longDescription": "Connect SAP Concur via OAuth 2.0. Manage expense reports and line items, allocations, attendees, comments, exceptions, quick expenses, receipts, travel requests and expected expenses, cash advances, itineraries, user identities, custom lists, budgets, exchange rates, and purchase requests across every Concur datacenter.", + "bgColor": "#FFFFFF", + "iconName": "SapConcurIcon", + "docsUrl": "https://docs.sim.ai/tools/sap_concur", + "operations": [ + { + "name": "List Expense Reports", + "description": "List expense reports (GET /api/v3.0/expense/reports). Returns a v3 envelope with Items and NextPage." + }, + { + "name": "Get Expense Report", + "description": "Retrieve a single expense report header by id via Expense Report v4 (/expensereports/v4/users/{userId}/context/{contextType}/reports/{reportId})." + }, + { + "name": "Create Expense Report", + "description": "Create an expense report (POST /expensereports/v4/users/{userId}/context/{contextType}/reports — supported contexts: TRAVELER, PROXY). Required body fields: name, policyId." + }, + { + "name": "Update Expense Report", + "description": "Update an unsubmitted expense report (PATCH /expensereports/v4/users/{userId}/context/{contextType}/reports/{reportId} — supported contexts: TRAVELER, PROXY). Body fields: businessPurpose, comment, customData, name, etc." + }, + { + "name": "Delete Expense Report", + "description": "Delete an expense report (DELETE /expensereports/v4/reports/{reportId})." + }, + { + "name": "Submit Expense Report", + "description": "Submit an expense report into the workflow via Expense Report v4 (PATCH /expensereports/v4/users/{userId}/reports/{reportId}/submit)." + }, + { + "name": "Recall Expense Report", + "description": "Recall a submitted expense report (PATCH /expensereports/v4/users/{userId}/context/{contextType}/reports/{reportId}/recall — supported contexts: TRAVELER, PROXY). No request body is required." + }, + { + "name": "Approve Expense Report", + "description": "Approve an expense report as a manager (PATCH /expensereports/v4/reports/{reportId}/approve). Required body field: comment." + }, + { + "name": "Send Back Expense Report", + "description": "Send back an expense report to the employee (PATCH /expensereports/v4/reports/{reportId}/sendBack). Required body field: comment." + }, + { + "name": "List Reports To Approve", + "description": "List expense reports awaiting approval (GET /expensereports/v4/users/{userId}/context/MANAGER/reportsToApprove)." + }, + { + "name": "List Expenses", + "description": "List expenses on a report (GET /expensereports/v4/users/{userId}/context/{contextType}/reports/{reportId}/expenses)." + }, + { + "name": "Get Expense", + "description": "Get a single expense (GET /expensereports/v4/users/{userId}/context/{contextType}/reports/{reportId}/expenses/{expenseId})." + }, + { + "name": "Update Expense", + "description": "Update an expense (PATCH /expensereports/v4/reports/{reportId}/expenses/{expenseId})." + }, + { + "name": "Delete Expense", + "description": "Delete an expense (DELETE /expensereports/v4/reports/{reportId}/expenses/{expenseId})." + }, + { + "name": "Get Itemizations", + "description": "Get expense itemizations (GET /expensereports/v4/users/{userId}/context/{contextType}/reports/{reportId}/expenses/{expenseId}/itemizations)." + }, + { + "name": "List Allocations", + "description": "List allocations on an expense (GET /expensereports/v4/users/{userId}/context/{contextType}/reports/{reportId}/expenses/{expenseId}/allocations)." + }, + { + "name": "Get Allocation", + "description": "Get a single allocation (GET /expensereports/v4/users/{userId}/context/{contextType}/reports/{reportId}/allocations/{allocationId})." + }, + { + "name": "Update Allocation", + "description": "Update an allocation (PATCH /expensereports/v4/users/{userId}/context/{contextType}/reports/{reportId}/allocations/{allocationId})." + }, + { + "name": "List Attendee Associations", + "description": "List attendees associated with an expense (GET /expensereports/v4/users/{userId}/context/{contextType}/reports/{reportId}/expenses/{expenseId}/attendees)." + }, + { + "name": "Associate Attendees", + "description": "Associate attendees with an expense (POST /expensereports/v4/users/{userId}/context/{contextType}/reports/{reportId}/expenses/{expenseId}/attendees)." + }, + { + "name": "Remove All Attendees", + "description": "Remove all attendees from an expense (DELETE /expensereports/v4/users/{userId}/context/{contextType}/reports/{reportId}/expenses/{expenseId}/attendees)." + }, + { + "name": "List Report Comments", + "description": "List comments on a report (GET /expensereports/v4/users/{userId}/context/{contextType}/reports/{reportId}/comments)." + }, + { + "name": "Create Report Comment", + "description": "Create a comment on a report (POST /expensereports/v4/users/{userId}/context/{contextType}/reports/{reportId}/comments)." + }, + { + "name": "List Exceptions", + "description": "List exceptions on a report (GET /expensereports/v4/users/{userId}/context/{contextType}/reports/{reportId}/exceptions)." + }, + { + "name": "Create Quick Expense", + "description": "Create a quick expense (POST /quickexpense/v4/users/{userId}/context/TRAVELER/quickexpenses)." + }, + { + "name": "Create Quick Expense (With Image)", + "description": "Create a quick expense with an attached image (POST /quickexpense/v4/users/{userId}/context/{contextType}/quickexpenses/image)." + }, + { + "name": "List Receipts", + "description": "List receipts for a user (GET /receipts/v4/users/{userId})." + }, + { + "name": "Get Receipt", + "description": "Get a single receipt by ID (GET /receipts/v4/{receiptId})." + }, + { + "name": "Get Receipt Status", + "description": "Get receipt processing status (GET /receipts/v4/status/{receiptId})." + }, + { + "name": "Upload Receipt Image", + "description": "Upload an image-only receipt (POST /receipts/v4/users/{userId}/image-only-receipts)." + }, + { + "name": "List Travel Requests", + "description": "List travel requests (GET /travelrequest/v4/requests)." + }, + { + "name": "Get Travel Request", + "description": "Get a single travel request (GET /travelrequest/v4/requests/{requestUuid})." + }, + { + "name": "Create Travel Request", + "description": "Create a travel request (POST /travelrequest/v4/requests)." + }, + { + "name": "Update Travel Request", + "description": "Update a travel request (PUT /travelrequest/v4/requests/{requestUuid})." + }, + { + "name": "Delete Travel Request", + "description": "Delete a travel request (DELETE /travelrequest/v4/requests/{requestUuid})." + }, + { + "name": "Move Travel Request (Workflow Action)", + "description": "Move a travel request through workflow (POST /travelrequest/v4/requests/{requestUuid}/{action}). Valid actions: submit, recall, cancel, approve, sendback, close, reopen." + }, + { + "name": "List Travel Request Comments", + "description": "List comments on a travel request (GET /travelrequest/v4/requests/{requestUuid}/comments)." + }, + { + "name": "Get Request Cash Advance", + "description": "Get a single cash advance assigned to a travel request (GET /travelrequest/v4/cashadvances/{cashAdvanceUuid})." + }, + { + "name": "Create Expected Expense", + "description": "Create an expected expense on a travel request (POST /travelrequest/v4/requests/{requestUuid}/expenses)." + }, + { + "name": "List Expected Expenses", + "description": "List expected expenses on a travel request (GET /travelrequest/v4/requests/{requestUuid}/expenses)." + }, + { + "name": "Get Expected Expense", + "description": "Get an expected expense (GET /travelrequest/v4/expenses/{expenseUuid})." + }, + { + "name": "Update Expected Expense", + "description": "Update an expected expense (PUT /travelrequest/v4/expenses/{expenseUuid})." + }, + { + "name": "Delete Expected Expense", + "description": "Delete an expected expense (DELETE /travelrequest/v4/expenses/{expenseUuid})." + }, + { + "name": "Create Cash Advance", + "description": "Create a cash advance (POST /cashadvance/v4.1/cashadvances)." + }, + { + "name": "Get Cash Advance", + "description": "Get a cash advance (GET /cashadvance/v4.1/cashadvances/{cashAdvanceId})." + }, + { + "name": "Issue Cash Advance", + "description": "Issue a cash advance (POST /cashadvance/v4.1/cashadvances/{cashAdvanceId}/issue)." + }, + { + "name": "List Itineraries (Trips)", + "description": "List travel trips/itineraries (GET /api/travel/trip/v1.1)." + }, + { + "name": "Get Itinerary (Trip)", + "description": "Get a single trip/itinerary (GET /api/travel/trip/v1.1/{tripID})." + }, + { + "name": "List Users", + "description": "List Concur user identities (GET /profile/identity/v4.1/Users)." + }, + { + "name": "Get User", + "description": "Get a single user by UUID (GET /profile/identity/v4.1/Users/{id})." + }, + { + "name": "Create User", + "description": "Create a new user identity (POST /profile/identity/v4.1/Users)." + }, + { + "name": "Update User (PATCH)", + "description": "Patch a user identity (PATCH /profile/identity/v4.1/Users/{id})." + }, + { + "name": "Delete User", + "description": "Delete a user identity (DELETE /profile/identity/v4.1/Users/{id})." + }, + { + "name": "Search Users", + "description": "Search users via SCIM .search endpoint (POST /profile/identity/v4.1/Users/.search)." + }, + { + "name": "List Lists", + "description": "List custom lists (GET /list/v4/lists)." + }, + { + "name": "Get List", + "description": "Get a single custom list (GET /list/v4/lists/{listId})." + }, + { + "name": "List List Items", + "description": "List the top-level items (children) for a custom list (GET /list/v4/lists/{listId}/children)." + }, + { + "name": "Get List Item", + "description": "Get a single list item (GET /list/v4/items/{itemId})." + }, + { + "name": "Create List Item", + "description": "Create a list item (POST /list/v4/items)." + }, + { + "name": "Update List Item", + "description": "Update a list item (PUT /list/v4/items/{itemId})." + }, + { + "name": "Delete List Item", + "description": "Delete a list item (DELETE /list/v4/items/{itemId})." + }, + { + "name": "List Budgets", + "description": "List budget item headers (GET /budget/v4/budgetItemHeader)." + }, + { + "name": "Get Budget", + "description": "Get a budget item header by ID (GET /budget/v4/budgetItemHeader/{id})." + }, + { + "name": "List Budget Categories", + "description": "List budget categories (GET /budget/v4/budgetCategory)." + }, + { + "name": "Upload Exchange Rates", + "description": "Bulk upload up to 100 custom exchange rates (POST /exchangerate/v4/rates). Body contains a currency_sets array, each with from_crn_code, to_crn_code, start_date (YYYY-MM-DD), and rate." + }, + { + "name": "Create Purchase Request", + "description": "Create a purchase request (POST /purchaserequest/v4/purchaserequests)." + }, + { + "name": "Get Purchase Request", + "description": "Get a purchase request by ID (GET /purchaserequest/v4/purchaserequests/{id})." + }, + { + "name": "Get Travel Profile", + "description": "Get a travel profile (GET /api/travelprofile/v2.0/profile). Returns the calling user by default; pass userid_type and userid_value to impersonate." + }, + { + "name": "List Travel Profiles Summary", + "description": "List travel profile summaries (GET /api/travelprofile/v2.0/summary). LastModifiedDate is required by Concur." + }, + { + "name": "Search Locations", + "description": "Search Concur location reference data (GET /localities/v5/locations)." + } + ], + "operationCount": 70, + "triggers": [], + "triggerCount": 0, + "authType": "none", + "category": "tools", + "integrationTypes": ["other", "developer-tools"], + "tags": ["automation"] + }, { "type": "sap_s4hana", "slug": "sap-s4hana", @@ -11431,7 +11805,7 @@ }, { "name": "Update Business Partner", - "description": "Update fields on an A_BusinessPartner entity in SAP S/4HANA Cloud (API_BUSINESS_PARTNER). PATCH only sends the fields you provide; existing values are preserved. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET to avoid lost updates." + "description": "Update fields on an A_BusinessPartner entity in SAP S/4HANA Cloud (API_BUSINESS_PARTNER). Uses HTTP MERGE (OData v2 partial update) — only the fields you provide are written; existing values are preserved. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET to avoid lost updates. Deep updates on nested associations (e.g. to_BusinessPartnerAddress) are not supported by SAP (KBA 2833338) — use the dedicated child endpoints." }, { "name": "List Customers", @@ -11443,7 +11817,7 @@ }, { "name": "Update Customer", - "description": "Update fields on an A_Customer entity in SAP S/4HANA Cloud (API_BUSINESS_PARTNER). PATCH only sends the fields you provide; existing values are preserved. A_Customer PATCH is limited to modifiable fields such as OrderIsBlockedForCustomer, DeliveryIsBlock, BillingIsBlockedForCustomer, PostingIsBlocked, and DeletionIndicator. If-Match defaults to a wildcard - for safe concurrent updates pass the ETag from a prior GET to avoid lost updates." + "description": "Update fields on an A_Customer entity in SAP S/4HANA Cloud (API_BUSINESS_PARTNER). Uses HTTP MERGE (OData v2 partial update) — only the fields you provide are written; existing values are preserved. A_Customer is limited to modifiable fields such as OrderIsBlockedForCustomer, DeliveryIsBlocked, BillingIsBlockedForCustomer (Edm.String reason codes like " }, { "name": "List Suppliers", @@ -11455,7 +11829,7 @@ }, { "name": "Update Supplier", - "description": "Update fields on an A_Supplier entity in SAP S/4HANA Cloud (API_BUSINESS_PARTNER). PATCH only sends the fields you provide; existing values are preserved. A_Supplier PATCH is limited to modifiable fields such as PostingIsBlocked, PurchasingIsBlocked, PaymentIsBlockedForSupplier, DeletionIndicator, and SupplierAccountGroup. If-Match defaults to a wildcard - for safe concurrent updates pass the ETag from a prior GET to avoid lost updates." + "description": "" }, { "name": "List Sales Orders", @@ -11471,7 +11845,7 @@ }, { "name": "Update Sales Order", - "description": "Update fields on an A_SalesOrder entity in SAP S/4HANA Cloud (API_SALES_ORDER_SRV). PATCH only sends the fields you provide; existing values are preserved. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET to avoid lost updates." + "description": "" }, { "name": "Delete Sales Order", @@ -11511,7 +11885,7 @@ }, { "name": "Update Product", - "description": "Update fields on an A_Product entity in SAP S/4HANA Cloud (API_PRODUCT_SRV). PATCH only sends the fields you provide; existing values are preserved. Flat scalar header fields only — deep/multi-entity updates across navigation properties are not supported by API_PRODUCT_SRV PATCH/PUT (see SAP KBA 2833338); update child entities (plant, valuation, sales data, etc.) via their own endpoints. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET." + "description": "" }, { "name": "List Material Stock", @@ -11539,7 +11913,7 @@ }, { "name": "Update Purchase Requisition", - "description": "Update fields on an A_PurchaseRequisitionHeader entity in SAP S/4HANA Cloud (API_PURCHASEREQ_PROCESS_SRV; deprecated since S/4HANA 2402, successor is API_PURCHASEREQUISITION_2 OData v4). PATCH only sends the fields you provide; existing values are preserved. If-Match defaults to a wildcard - for safe concurrent updates pass the ETag from a prior GET to avoid lost updates." + "description": "" }, { "name": "List Purchase Orders", @@ -11555,7 +11929,7 @@ }, { "name": "Update Purchase Order", - "description": "Update fields on an A_PurchaseOrder entity in SAP S/4HANA Cloud (API_PURCHASEORDER_PROCESS_SRV). PATCH only sends the fields you provide; existing values are preserved. If-Match defaults to a wildcard (unconditional) — for safe concurrent updates pass the ETag from a prior GET to avoid lost updates." + "description": "Update fields on an A_PurchaseOrder header in SAP S/4HANA Cloud (API_PURCHASEORDER_PROCESS_SRV). Uses HTTP MERGE (OData v2 partial update) — only the fields you provide are written; existing values are preserved. Header-only — line-item changes are not supported via deep update on the header (SAP KBA 2833338); use the A_PurchaseOrderItem entity directly to modify items. If-Match defaults to a wildcard - for safe concurrent updates pass the ETag from a prior GET to avoid lost updates." }, { "name": "List Supplier Invoices", @@ -11567,7 +11941,7 @@ }, { "name": "OData Query (advanced)", - "description": "Make an arbitrary OData v2 call against any SAP S/4HANA Cloud whitelisted Communication Scenario. Use when no dedicated tool exists for the entity. The proxy handles auth, CSRF, and OData unwrapping." + "description": "Make an arbitrary OData v2 call against any SAP S/4HANA Cloud whitelisted Communication Scenario. Use when no dedicated tool exists for the entity. The proxy handles auth, CSRF, and OData unwrapping. For write operations (POST/PUT/PATCH/MERGE/DELETE), pass an If-Match ETag obtained from a prior GET to avoid lost updates; misuse will mutate production data." } ], "operationCount": 38, @@ -11869,9 +12243,9 @@ "tags": ["cloud", "automation"] }, { - "type": "sharepoint", + "type": "sharepoint_v2", "slug": "sharepoint", - "name": "Sharepoint", + "name": "SharePoint", "description": "Work with pages and lists", "longDescription": "Integrate SharePoint into the workflow. Read/create pages, list sites, and work with lists (read, create, update items). Requires OAuth.", "bgColor": "#E0E0E0", @@ -11899,11 +12273,11 @@ "description": "Get metadata (and optionally columns/items) for a SharePoint list" }, { - "name": "Update List", + "name": "Update List Item", "description": "Update the properties (fields) on a SharePoint list item" }, { - "name": "Add List Items", + "name": "Add List Item", "description": "Add a new item to a SharePoint list" }, { diff --git a/apps/sim/app/(landing)/privacy/page.tsx b/apps/sim/app/(landing)/privacy/page.tsx index 44140c62bb6..1081c090b82 100644 --- a/apps/sim/app/(landing)/privacy/page.tsx +++ b/apps/sim/app/(landing)/privacy/page.tsx @@ -3,6 +3,8 @@ import Link from 'next/link' import { getEnv } from '@/lib/core/config/env' import { ExternalRedirect, LegalLayout } from '@/app/(landing)/components' +export const dynamic = 'force-dynamic' + export const metadata: Metadata = { title: 'Privacy Policy', description: diff --git a/apps/sim/app/(landing)/terms/page.tsx b/apps/sim/app/(landing)/terms/page.tsx index 08c16aeaa14..5d49f2bc52a 100644 --- a/apps/sim/app/(landing)/terms/page.tsx +++ b/apps/sim/app/(landing)/terms/page.tsx @@ -3,6 +3,8 @@ import Link from 'next/link' import { getEnv } from '@/lib/core/config/env' import { ExternalRedirect, LegalLayout } from '@/app/(landing)/components' +export const dynamic = 'force-dynamic' + export const metadata: Metadata = { title: 'Terms of Service', description: diff --git a/apps/sim/app/api/auth/trello/authorize/route.ts b/apps/sim/app/api/auth/trello/authorize/route.ts index 48d2f10f2a6..fd6aa17d0f3 100644 --- a/apps/sim/app/api/auth/trello/authorize/route.ts +++ b/apps/sim/app/api/auth/trello/authorize/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { generateShortId } from '@sim/utils/id' import { type NextRequest, NextResponse } from 'next/server' import { authorizeTrelloContract } from '@/lib/api/contracts/oauth-connections' import { parseRequest } from '@/lib/api/server' @@ -12,6 +13,10 @@ const logger = createLogger('TrelloAuthorize') export const dynamic = 'force-dynamic' +const TRELLO_STATE_COOKIE = 'trello_oauth_state' +const TRELLO_STATE_COOKIE_PATH = '/api/auth/trello' +const TRELLO_STATE_COOKIE_MAX_AGE_SECONDS = 60 * 10 + export const GET = withRouteHandler(async (request: NextRequest) => { try { const session = await getSession() @@ -30,7 +35,9 @@ export const GET = withRouteHandler(async (request: NextRequest) => { } const baseUrl = getBaseUrl() - const returnUrl = `${baseUrl}/api/auth/trello/callback` + const state = generateShortId(32) + const returnUrl = new URL('/api/auth/trello/callback', baseUrl) + returnUrl.searchParams.set('state', state) const scope = getCanonicalScopesForProvider('trello').join(',') const authUrl = new URL('https://trello.com/1/authorize') @@ -40,9 +47,17 @@ export const GET = withRouteHandler(async (request: NextRequest) => { authUrl.searchParams.set('callback_method', 'fragment') authUrl.searchParams.set('response_type', 'token') authUrl.searchParams.set('scope', scope) - authUrl.searchParams.set('return_url', returnUrl) + authUrl.searchParams.set('return_url', returnUrl.toString()) - return NextResponse.redirect(authUrl.toString()) + const response = NextResponse.redirect(authUrl.toString()) + response.cookies.set(TRELLO_STATE_COOKIE, state, { + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'lax', + maxAge: TRELLO_STATE_COOKIE_MAX_AGE_SECONDS, + path: TRELLO_STATE_COOKIE_PATH, + }) + return response } catch (error) { logger.error('Error initiating Trello authorization:', error) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) diff --git a/apps/sim/app/api/auth/trello/callback/route.ts b/apps/sim/app/api/auth/trello/callback/route.ts index 6f0aecd12f5..9ef74c2081e 100644 --- a/apps/sim/app/api/auth/trello/callback/route.ts +++ b/apps/sim/app/api/auth/trello/callback/route.ts @@ -1,16 +1,54 @@ +import { createLogger } from '@sim/logger' import { type NextRequest, NextResponse } from 'next/server' import { trelloCallbackContract } from '@/lib/api/contracts/oauth-connections' import { parseRequest } from '@/lib/api/server' import { getBaseUrl } from '@/lib/core/utils/urls' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +const logger = createLogger('TrelloCallback') + export const dynamic = 'force-dynamic' +const TRELLO_STATE_COOKIE = 'trello_oauth_state' + +function escapeForJsString(value: string): string { + return value.replace(/[\\'"<>&\r\n\u2028\u2029]/g, (ch) => { + return `\\u${ch.charCodeAt(0).toString(16).padStart(4, '0')}` + }) +} + +function renderErrorPage(baseUrl: string, redirectQuery: string) { + return new NextResponse( + `Trello connection failed

Trello connection failed. Redirecting...

`, + { + status: 400, + headers: { + 'Content-Type': 'text/html; charset=utf-8', + 'Cache-Control': 'no-store, no-cache, must-revalidate', + }, + } + ) +} + export const GET = withRouteHandler(async (request: NextRequest) => { const parsed = await parseRequest(trelloCallbackContract, request, {}) if (!parsed.success) return parsed.response const baseUrl = getBaseUrl() + const queryState = parsed.data.query.state + const cookieState = request.cookies.get(TRELLO_STATE_COOKIE)?.value + + if (!queryState || !cookieState || queryState !== cookieState) { + logger.warn('Trello callback rejected: state mismatch or missing state', { + hasQueryState: Boolean(queryState), + hasCookieState: Boolean(cookieState), + }) + const response = renderErrorPage(baseUrl, 'error=trello_state_mismatch') + response.cookies.delete({ name: TRELLO_STATE_COOKIE, path: '/api/auth/trello' }) + return response + } + + const safeState = escapeForJsString(queryState) return new NextResponse( ` @@ -97,7 +135,7 @@ export const GET = withRouteHandler(async (request: NextRequest) => { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', - body: JSON.stringify({ token: token }) + body: JSON.stringify({ token: token, state: '${safeState}' }) }) .then(response => response.json()) .then(data => { diff --git a/apps/sim/app/api/auth/trello/store/route.ts b/apps/sim/app/api/auth/trello/store/route.ts index d62237e7015..156ed9a65d6 100644 --- a/apps/sim/app/api/auth/trello/store/route.ts +++ b/apps/sim/app/api/auth/trello/store/route.ts @@ -16,6 +16,14 @@ const logger = createLogger('TrelloStore') export const dynamic = 'force-dynamic' +const TRELLO_STATE_COOKIE = 'trello_oauth_state' +const TRELLO_STATE_COOKIE_PATH = '/api/auth/trello' + +function clearStateCookie(response: NextResponse) { + response.cookies.delete({ name: TRELLO_STATE_COOKIE, path: TRELLO_STATE_COOKIE_PATH }) + return response +} + export const POST = withRouteHandler(async (request: NextRequest) => { try { const session = await getSession() @@ -26,7 +34,21 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const parsed = await parseRequest(storeTrelloTokenContract, request, {}) if (!parsed.success) return parsed.response - const { token } = parsed.data.body + const { token, state } = parsed.data.body + + const cookieState = request.cookies.get(TRELLO_STATE_COOKIE)?.value + if (!cookieState || cookieState !== state) { + logger.warn('Trello store rejected: state mismatch', { + hasCookieState: Boolean(cookieState), + userId: session.user.id, + }) + return clearStateCookie( + NextResponse.json( + { success: false, error: 'Invalid or expired authorization state' }, + { status: 400 } + ) + ) + } const apiKey = env.TRELLO_API_KEY if (!apiKey) { @@ -123,7 +145,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { } } - return NextResponse.json({ success: true }) + return clearStateCookie(NextResponse.json({ success: true })) } catch (error) { logger.error('Error storing Trello token:', error) return NextResponse.json({ success: false, error: 'Internal server error' }, { status: 500 }) diff --git a/apps/sim/app/api/chat/manage/[id]/route.ts b/apps/sim/app/api/chat/manage/[id]/route.ts index 933636dd12b..8a70bcb9775 100644 --- a/apps/sim/app/api/chat/manage/[id]/route.ts +++ b/apps/sim/app/api/chat/manage/[id]/route.ts @@ -110,6 +110,10 @@ export const PATCH = withRouteHandler( outputConfigs, } = validatedData + if (workflowId && workflowId !== existingChat[0].workflowId) { + return createErrorResponse('Changing the workflow of a chat deployment is not allowed', 400) + } + if (identifier && identifier !== existingChat[0].identifier) { const existingIdentifier = await db .select() @@ -156,7 +160,6 @@ export const PATCH = withRouteHandler( updatedAt: new Date(), } - if (workflowId) updateData.workflowId = workflowId if (identifier) updateData.identifier = identifier if (title) updateData.title = title if (description !== undefined) updateData.description = description diff --git a/apps/sim/app/api/function/execute/route.test.ts b/apps/sim/app/api/function/execute/route.test.ts index 8cdf1ca8d98..9f4c74df5da 100644 --- a/apps/sim/app/api/function/execute/route.test.ts +++ b/apps/sim/app/api/function/execute/route.test.ts @@ -538,6 +538,36 @@ describe('Function Execute API Route', () => { expect(data.error).toContain('undefinedVariable is not defined') }) + it('should show original source code when resolved block references cause syntax errors', async () => { + mockExecuteInIsolatedVM.mockResolvedValueOnce({ + result: null, + stdout: '', + error: { + message: 'Unexpected identifier "globalThis"', + name: 'SyntaxError', + line: 1, + column: 7, + lineContent: 'retur globalThis["__blockRef_0"]', + }, + }) + + const req = createMockRequest('POST', { + code: 'retur globalThis["__blockRef_0"]', + sourceCode: 'retur ', + contextVariables: { __blockRef_0: 'value' }, + timeout: 5000, + }) + + const response = await POST(req) + const data = await response.json() + + expect(response.status).toBe(422) + expect(data.success).toBe(false) + expect(data.error).toContain('Line 1: `retur `') + expect(data.error).not.toContain('globalThis') + expect(data.debug.lineContent).toBe('retur ') + }) + it('should handle thrown errors gracefully', async () => { const req = createMockRequest('POST', { code: 'throw new Error("Custom error message");', diff --git a/apps/sim/app/api/function/execute/route.ts b/apps/sim/app/api/function/execute/route.ts index 89109b482ef..fcfda730c4b 100644 --- a/apps/sim/app/api/function/execute/route.ts +++ b/apps/sim/app/api/function/execute/route.ts @@ -34,6 +34,59 @@ const TAG_PATTERN = createReferencePattern() const E2B_JS_WRAPPER_LINES = 3 const E2B_PYTHON_WRAPPER_LINES = 1 +/** Matches valid JS identifier names (letters, digits, underscore; no leading digit). */ +const SAFE_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*$/ + +/** ES2023 reserved words — using these as `const` variable names produces a SyntaxError. */ +const JS_RESERVED_WORDS = new Set([ + 'break', + 'case', + 'catch', + 'class', + 'const', + 'continue', + 'debugger', + 'default', + 'delete', + 'do', + 'else', + 'export', + 'extends', + 'false', + 'finally', + 'for', + 'function', + 'if', + 'import', + 'in', + 'instanceof', + 'let', + 'new', + 'null', + 'return', + 'static', + 'super', + 'switch', + 'this', + 'throw', + 'true', + 'try', + 'typeof', + 'var', + 'void', + 'while', + 'with', + 'yield', + 'enum', + 'await', + 'implements', + 'interface', + 'package', + 'private', + 'protected', + 'public', +]) + type TypeScriptModule = typeof import('typescript') let typescriptModulePromise: Promise | null = null @@ -357,6 +410,30 @@ function createUserFriendlyErrorMessage( return errorMessage } +function getErrorDisplayCode(sourceCode: string | undefined, resolvedCode: string): string { + return sourceCode && sourceCode.length > 0 ? sourceCode : resolvedCode +} + +function getLineContent(code: string, line: number | undefined): string | undefined { + if (line === undefined || line < 1) { + return undefined + } + + return code.split('\n')[line - 1]?.trim() +} + +function getErrorDisplayMessage( + message: string, + sourceCode: string | undefined, + resolvedCode: string +): string { + if (!sourceCode || sourceCode === resolvedCode || !resolvedCode.includes('__blockRef_')) { + return message + } + + return message.replace(/\s+["']globalThis["']/g, '') +} + function resolveWorkflowVariables( code: string, workflowVariables: Record, @@ -587,6 +664,26 @@ function cleanStdout(stdout: string): string { return stdout } +/** + * Serializes a value for use as a shell environment variable. Strings pass through + * unchanged; primitives are coerced via `String`; objects, arrays, and other complex + * values are JSON-stringified so that referencing them via `$VAR` yields a useful + * representation instead of `[object Object]`. `null`/`undefined` become an empty + * string to match POSIX env semantics. + */ +function serializeForShellEnv(value: unknown, nullValue = ''): string { + if (value === null || value === undefined) return nullValue + if (typeof value === 'string') return value + if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') { + return String(value) + } + try { + return JSON.stringify(value) ?? '' + } catch { + return String(value) + } +} + async function maybeExportSandboxFileToWorkspace(args: { authUserId: string workflowId?: string @@ -694,6 +791,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { let stdout = '' let userCodeStartLine = 3 // Default value for error reporting let resolvedCode = '' // Store resolved code for error reporting + let sourceCodeForErrors: string | undefined try { const auth = await checkInternalAuth(req) @@ -710,6 +808,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { const { code, + sourceCode, params = {}, timeout = DEFAULT_EXECUTION_TIMEOUT_MS, language = DEFAULT_CODE_LANGUAGE, @@ -722,11 +821,13 @@ export const POST = withRouteHandler(async (req: NextRequest) => { blockNameMapping = {}, blockOutputSchemas = {}, workflowVariables = {}, + contextVariables: preResolvedContextVariables = {}, workflowId, workspaceId, isCustomTool = false, _sandboxFiles, } = body + sourceCodeForErrors = sourceCode const executionParams = { ...params } executionParams._context = undefined @@ -746,6 +847,10 @@ export const POST = withRouteHandler(async (req: NextRequest) => { // For shell, env vars are injected as OS env vars via shellEnvs. // Replace {{VAR}} placeholders with $VAR so the shell can access them natively. resolvedCode = code.replace(/\{\{([A-Za-z_][A-Za-z0-9_]*)\}\}/g, '$$$1') + // Carry pre-resolved block output variables (e.g. __blockRef_N) so they can be + // injected as shell env vars below. The executor replaces block references in the + // code with these names, so the values must be present at runtime. + contextVariables = { ...preResolvedContextVariables } } else { const codeResolution = resolveCodeVariables( code, @@ -758,7 +863,10 @@ export const POST = withRouteHandler(async (req: NextRequest) => { lang ) resolvedCode = codeResolution.resolvedCode - contextVariables = codeResolution.contextVariables + // Merge pre-resolved block output variables from the executor. These take precedence + // because they were produced by the resolver using full execution-state context + // (including loop/parallel scope) and should not be overwritten. + contextVariables = { ...codeResolution.contextVariables, ...preResolvedContextVariables } } let jsImports = '' @@ -783,10 +891,10 @@ export const POST = withRouteHandler(async (req: NextRequest) => { const shellEnvs: Record = {} for (const [k, v] of Object.entries(envVars)) { - shellEnvs[k] = String(v) + shellEnvs[k] = serializeForShellEnv(v) } for (const [k, v] of Object.entries(contextVariables)) { - shellEnvs[k] = String(v) + shellEnvs[k] = serializeForShellEnv(v, 'null') } logger.info(`[${requestId}] E2B shell execution`, { @@ -893,7 +1001,9 @@ export const POST = withRouteHandler(async (req: NextRequest) => { prologue += `const environmentVariables = JSON.parse(${JSON.stringify(JSON.stringify(envVars))});\n` prologueLineCount++ for (const [k, v] of Object.entries(contextVariables)) { - prologue += `const ${k} = ${formatLiteralForCode(v, 'javascript')};\n` + prologue += `globalThis[${JSON.stringify(k)}] = ${formatLiteralForCode(v, 'javascript')};\n` + prologue += `const ${k} = globalThis[${JSON.stringify(k)}];\n` + prologueLineCount++ prologueLineCount++ } @@ -936,11 +1046,12 @@ export const POST = withRouteHandler(async (req: NextRequest) => { }) if (e2bError) { + const errorDisplayCode = getErrorDisplayCode(sourceCodeForErrors, resolvedCode) const { formattedError, cleanedOutput } = formatE2BError( - e2bError, + getErrorDisplayMessage(e2bError, sourceCodeForErrors, resolvedCode), e2bStdout, lang, - resolvedCode, + errorDisplayCode, prologueLineCount + importLineCount ) return NextResponse.json( @@ -1018,11 +1129,12 @@ export const POST = withRouteHandler(async (req: NextRequest) => { }) if (e2bError) { + const errorDisplayCode = getErrorDisplayCode(sourceCodeForErrors, resolvedCode) const { formattedError, cleanedOutput } = formatE2BError( - e2bError, + getErrorDisplayMessage(e2bError, sourceCodeForErrors, resolvedCode), e2bStdout, lang, - resolvedCode, + errorDisplayCode, prologueLineCount ) return NextResponse.json( @@ -1059,10 +1171,16 @@ export const POST = withRouteHandler(async (req: NextRequest) => { const executionMethod = 'isolated-vm' + const isSafeParamKey = (key: string) => SAFE_IDENTIFIER.test(key) && !JS_RESERVED_WORDS.has(key) + const wrapperLines = ['(async () => {', ' try {'] if (isCustomTool) { Object.keys(executionParams).forEach((key) => { - wrapperLines.push(` const ${key} = params.${key};`) + if (isSafeParamKey(key)) { + wrapperLines.push(` const ${key} = params.${key};`) + } else { + logger.warn('Skipping param key — not a safe JS identifier', { key, requestId }) + } }) } userCodeStartLine = wrapperLines.length + 1 @@ -1070,7 +1188,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { let codeToExecute = resolvedCode let prependedLineCount = 0 if (isCustomTool) { - const paramKeys = Object.keys(executionParams) + const paramKeys = Object.keys(executionParams).filter(isSafeParamKey) const paramDestructuring = paramKeys.map((key) => `const ${key} = params.${key};`).join('\n') codeToExecute = `${paramDestructuring}\n${resolvedCode}` prependedLineCount = paramKeys.length @@ -1103,13 +1221,16 @@ export const POST = withRouteHandler(async (req: NextRequest) => { let adjustedLineContent = ivmError.lineContent if (prependedLineCount > 0 && ivmError.line !== undefined) { adjustedLine = Math.max(1, ivmError.line - prependedLineCount) - const codeLines = resolvedCode.split('\n') - if (adjustedLine <= codeLines.length) { - adjustedLineContent = codeLines[adjustedLine - 1]?.trim() - } } + const errorDisplayCode = getErrorDisplayCode(sourceCodeForErrors, resolvedCode) + const displayMessage = getErrorDisplayMessage( + ivmError.message, + sourceCodeForErrors, + resolvedCode + ) + adjustedLineContent = getLineContent(errorDisplayCode, adjustedLine) ?? adjustedLineContent const enhancedError: EnhancedError = { - message: ivmError.message, + message: displayMessage, name: ivmError.name, stack: ivmError.stack, originalError: ivmError, @@ -1121,7 +1242,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { const userFriendlyErrorMessage = createUserFriendlyErrorMessage( enhancedError, requestId, - resolvedCode + errorDisplayCode ) const detailLogFn = isSystemError ? logger.error.bind(logger) : logger.warn.bind(logger) @@ -1172,11 +1293,12 @@ export const POST = withRouteHandler(async (req: NextRequest) => { executionTime, }) - const enhancedError = extractEnhancedError(error, userCodeStartLine, resolvedCode) + const errorDisplayCode = getErrorDisplayCode(sourceCodeForErrors, resolvedCode) + const enhancedError = extractEnhancedError(error, userCodeStartLine, errorDisplayCode) const userFriendlyErrorMessage = createUserFriendlyErrorMessage( enhancedError, requestId, - resolvedCode + errorDisplayCode ) logger.error(`[${requestId}] Enhanced error details`, { diff --git a/apps/sim/app/api/tools/agiloft/attach/route.ts b/apps/sim/app/api/tools/agiloft/attach/route.ts index d0ec62e0fd8..edcbdc4c0f3 100644 --- a/apps/sim/app/api/tools/agiloft/attach/route.ts +++ b/apps/sim/app/api/tools/agiloft/attach/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { agiloftAttachContract } from '@/lib/api/contracts/tools/agiloft' import { getValidationErrorMessage, parseRequest } from '@/lib/api/server' @@ -90,7 +91,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const agiloftResponse = await fetch(url, { method: 'PUT', headers: { - 'Content-Type': userFile.type || 'application/octet-stream', + 'Content-Type': 'application/octet-stream', Authorization: `Bearer ${token}`, }, body: new Uint8Array(fileBuffer), @@ -136,9 +137,6 @@ export const POST = withRouteHandler(async (request: NextRequest) => { } catch (error) { logger.error(`[${requestId}] Error attaching file to Agiloft:`, error) - return NextResponse.json( - { success: false, error: error instanceof Error ? error.message : 'Internal server error' }, - { status: 500 } - ) + return NextResponse.json({ success: false, error: toError(error).message }, { status: 500 }) } }) diff --git a/apps/sim/app/api/tools/agiloft/retrieve/route.ts b/apps/sim/app/api/tools/agiloft/retrieve/route.ts index 3f94c8bc739..64bd72daae8 100644 --- a/apps/sim/app/api/tools/agiloft/retrieve/route.ts +++ b/apps/sim/app/api/tools/agiloft/retrieve/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { agiloftRetrieveContract } from '@/lib/api/contracts/tools/agiloft' import { getValidationErrorMessage, parseRequest } from '@/lib/api/server' @@ -127,9 +128,6 @@ export const POST = withRouteHandler(async (request: NextRequest) => { } catch (error) { logger.error(`[${requestId}] Error retrieving Agiloft attachment:`, error) - return NextResponse.json( - { success: false, error: error instanceof Error ? error.message : 'Internal server error' }, - { status: 500 } - ) + return NextResponse.json({ success: false, error: toError(error).message }, { status: 500 }) } }) diff --git a/apps/sim/app/api/tools/microsoft-dataverse/upload-file/route.ts b/apps/sim/app/api/tools/microsoft-dataverse/upload-file/route.ts index ec1b0833ab2..31bf0d8fe07 100644 --- a/apps/sim/app/api/tools/microsoft-dataverse/upload-file/route.ts +++ b/apps/sim/app/api/tools/microsoft-dataverse/upload-file/route.ts @@ -3,6 +3,7 @@ import { type NextRequest, NextResponse } from 'next/server' import { dataverseUploadFileContract } from '@/lib/api/contracts/tools/microsoft' import { parseRequest } from '@/lib/api/server' import { checkInternalAuth } from '@/lib/auth/hybrid' +import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { processSingleFileToUserFile } from '@/lib/uploads/utils/file-utils' @@ -78,20 +79,26 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const baseUrl = validatedData.environmentUrl.replace(/\/$/, '') const uploadUrl = `${baseUrl}/api/data/v9.2/${validatedData.entitySetName}(${validatedData.recordId})/${validatedData.fileColumn}` - const response = await fetch(uploadUrl, { - method: 'PATCH', - headers: { - Authorization: `Bearer ${validatedData.accessToken}`, - 'Content-Type': 'application/octet-stream', - 'OData-MaxVersion': '4.0', - 'OData-Version': '4.0', - 'x-ms-file-name': validatedData.fileName, + const response = await secureFetchWithValidation( + uploadUrl, + { + method: 'PATCH', + headers: { + Authorization: `Bearer ${validatedData.accessToken}`, + 'Content-Type': 'application/octet-stream', + 'OData-MaxVersion': '4.0', + 'OData-Version': '4.0', + 'x-ms-file-name': validatedData.fileName, + }, + body: fileBuffer, }, - body: new Uint8Array(fileBuffer), - }) + 'environmentUrl' + ) if (!response.ok) { - const errorData = await response.json().catch(() => ({})) + const errorData = (await response.json().catch(() => ({}))) as { + error?: { message?: string } + } const errorMessage = errorData?.error?.message ?? `Dataverse API error: ${response.status} ${response.statusText}` diff --git a/apps/sim/app/api/tools/microsoft_excel/drives/route.ts b/apps/sim/app/api/tools/microsoft_excel/drives/route.ts index df884bea928..2e0d1d80e43 100644 --- a/apps/sim/app/api/tools/microsoft_excel/drives/route.ts +++ b/apps/sim/app/api/tools/microsoft_excel/drives/route.ts @@ -7,7 +7,7 @@ import { validatePathSegment, validateSharePointSiteId } from '@/lib/core/securi import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' -import { GRAPH_ID_PATTERN } from '@/tools/microsoft_excel/utils' +import { extractGraphError, GRAPH_ID_PATTERN } from '@/tools/microsoft_excel/utils' export const dynamic = 'force-dynamic' @@ -76,13 +76,8 @@ export const POST = withRouteHandler(async (request: NextRequest) => { }) if (!response.ok) { - const errorData = await response - .json() - .catch(() => ({ error: { message: 'Unknown error' } })) - return NextResponse.json( - { error: errorData.error?.message || 'Failed to fetch drive' }, - { status: response.status } - ) + const errorMessage = await extractGraphError(response) + return NextResponse.json({ error: errorMessage }, { status: response.status }) } const data: GraphDrive = await response.json() @@ -102,15 +97,12 @@ export const POST = withRouteHandler(async (request: NextRequest) => { }) if (!response.ok) { - const errorData = await response.json().catch(() => ({ error: { message: 'Unknown error' } })) + const errorMessage = await extractGraphError(response) logger.error(`[${requestId}] Microsoft Graph API error fetching drives`, { status: response.status, - error: errorData.error?.message, + error: errorMessage, }) - return NextResponse.json( - { error: errorData.error?.message || 'Failed to fetch drives' }, - { status: response.status } - ) + return NextResponse.json({ error: errorMessage }, { status: response.status }) } const data = await response.json() diff --git a/apps/sim/app/api/tools/microsoft_excel/sheets/route.ts b/apps/sim/app/api/tools/microsoft_excel/sheets/route.ts index 7a2c64cf6c3..19212aaa59a 100644 --- a/apps/sim/app/api/tools/microsoft_excel/sheets/route.ts +++ b/apps/sim/app/api/tools/microsoft_excel/sheets/route.ts @@ -6,7 +6,7 @@ import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' -import { getItemBasePath } from '@/tools/microsoft_excel/utils' +import { extractGraphError, getItemBasePath } from '@/tools/microsoft_excel/utils' export const dynamic = 'force-dynamic' @@ -73,18 +73,12 @@ export const GET = withRouteHandler(async (request: NextRequest) => { }) if (!worksheetsResponse.ok) { - const errorData = await worksheetsResponse - .text() - .then((text) => JSON.parse(text)) - .catch(() => ({ error: { message: 'Unknown error' } })) + const errorMessage = await extractGraphError(worksheetsResponse) logger.error(`[${requestId}] Microsoft Graph API error`, { status: worksheetsResponse.status, - error: errorData.error?.message || 'Failed to fetch worksheets', + error: errorMessage, }) - return NextResponse.json( - { error: errorData.error?.message || 'Failed to fetch worksheets' }, - { status: worksheetsResponse.status } - ) + return NextResponse.json({ error: errorMessage }, { status: worksheetsResponse.status }) } const data: WorksheetsResponse = await worksheetsResponse.json() diff --git a/apps/sim/app/api/tools/sap_concur/proxy/route.ts b/apps/sim/app/api/tools/sap_concur/proxy/route.ts new file mode 100644 index 00000000000..802d83be266 --- /dev/null +++ b/apps/sim/app/api/tools/sap_concur/proxy/route.ts @@ -0,0 +1,133 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { getValidationErrorMessage, isZodError } from '@/lib/api/server' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server' +import { generateRequestId } from '@/lib/core/utils/request' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { + assertSafeExternalUrl, + extractSapConcurError, + fetchSapConcurAccessToken, + SAP_CONCUR_OUTBOUND_FETCH_TIMEOUT_MS, + type SapConcurProxyRequest, + SapConcurProxyRequestSchema, +} from '@/app/api/tools/sap_concur/shared' + +export const dynamic = 'force-dynamic' + +const logger = createLogger('SapConcurProxyAPI') + +type ProxyRequest = SapConcurProxyRequest + +function buildApiUrl(geolocation: string, req: ProxyRequest): string { + const base = geolocation.replace(/\/+$/, '') + const subPath = req.path.startsWith('/') ? req.path : `/${req.path}` + const url = `${base}${subPath}` + + if (!req.query || Object.keys(req.query).length === 0) { + return url + } + const search = new URLSearchParams() + for (const [key, value] of Object.entries(req.query)) { + if (value === undefined || value === null) continue + search.append(key, String(value)) + } + const queryString = search.toString() + if (!queryString) return url + return url.includes('?') ? `${url}&${queryString}` : `${url}?${queryString}` +} + +interface Invocation { + status: number + body: unknown + raw: string +} + +async function callConcur( + req: ProxyRequest, + accessToken: string, + geolocation: string +): Promise { + const url = assertSafeExternalUrl(buildApiUrl(geolocation, req), 'apiUrl').toString() + const hasBody = req.body !== undefined && req.body !== null + const headers: Record = { + Authorization: `Bearer ${accessToken}`, + Accept: 'application/json', + } + if (hasBody) headers['Content-Type'] = req.contentType ?? 'application/json' + if (req.companyUuid) headers['concur-correlationid'] = req.companyUuid + + const response = await secureFetchWithValidation( + url, + { + method: req.method, + headers, + body: hasBody + ? typeof req.body === 'string' + ? req.body + : JSON.stringify(req.body) + : undefined, + timeout: SAP_CONCUR_OUTBOUND_FETCH_TIMEOUT_MS, + }, + 'apiUrl' + ) + + const raw = await response.text() + let parsed: unknown = null + if (raw.length > 0) { + try { + parsed = JSON.parse(raw) + } catch { + parsed = raw + } + } + return { status: response.status, body: parsed, raw } +} + +export const POST = withRouteHandler(async (request: NextRequest) => { + const requestId = generateRequestId() + + try { + const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) + if (!authResult.success) { + logger.warn(`[${requestId}] Unauthorized Concur proxy request: ${authResult.error}`) + return NextResponse.json( + { success: false, error: authResult.error || 'Authentication required' }, + { status: 401 } + ) + } + + // boundary-raw-json: internal proxy envelope validated by SapConcurProxyRequestSchema below; not a public boundary + const json = await request.json() + const proxyReq = SapConcurProxyRequestSchema.parse(json) + + const { accessToken, geolocation } = await fetchSapConcurAccessToken(proxyReq, requestId) + const invocation = await callConcur(proxyReq, accessToken, geolocation) + + if (invocation.status >= 200 && invocation.status < 300) { + const data = invocation.status === 204 ? null : invocation.body + return NextResponse.json({ success: true, output: { status: invocation.status, data } }) + } + + const message = extractSapConcurError(invocation.body, invocation.status) + logger.warn( + `[${requestId}] Concur API error (${invocation.status}) ${proxyReq.path}: ${message}` + ) + return NextResponse.json( + { success: false, error: message, status: invocation.status }, + { status: invocation.status } + ) + } catch (error) { + if (isZodError(error)) { + logger.warn(`[${requestId}] Validation error:`, error.issues) + return NextResponse.json( + { success: false, error: getValidationErrorMessage(error, 'Validation failed') }, + { status: 400 } + ) + } + logger.error(`[${requestId}] Unexpected Concur proxy error:`, error) + return NextResponse.json({ success: false, error: toError(error).message }, { status: 500 }) + } +}) diff --git a/apps/sim/app/api/tools/sap_concur/shared.ts b/apps/sim/app/api/tools/sap_concur/shared.ts new file mode 100644 index 00000000000..e395b4123af --- /dev/null +++ b/apps/sim/app/api/tools/sap_concur/shared.ts @@ -0,0 +1,305 @@ +import { createHash } from 'node:crypto' +import { createLogger } from '@sim/logger' +import { z } from 'zod' +import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server' +import { FileInputSchema } from '@/lib/uploads/utils/file-schemas' + +const logger = createLogger('SapConcurShared') + +export const SAP_CONCUR_ALLOWED_DATACENTERS = new Set([ + 'us.api.concursolutions.com', + 'us2.api.concursolutions.com', + 'eu.api.concursolutions.com', + 'eu2.api.concursolutions.com', + 'cn.api.concursolutions.com', + 'emea.api.concursolutions.com', +]) + +export const SapConcurDatacenterSchema = z + .string() + .min(1) + .refine((d) => SAP_CONCUR_ALLOWED_DATACENTERS.has(d), { + message: `datacenter must be one of: ${Array.from(SAP_CONCUR_ALLOWED_DATACENTERS).join(', ')}`, + }) + +export const SapConcurGrantTypeSchema = z.enum(['client_credentials', 'password']) + +export const SapConcurAuthSchema = z.object({ + datacenter: SapConcurDatacenterSchema.default('us.api.concursolutions.com'), + grantType: SapConcurGrantTypeSchema.default('client_credentials'), + clientId: z.string().min(1, 'clientId is required'), + clientSecret: z.string().min(1, 'clientSecret is required'), + username: z.string().optional(), + password: z.string().optional(), + companyUuid: z.string().optional(), +}) + +export type SapConcurAuth = z.infer + +export const SapConcurHttpMethod = z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']) + +export const SapConcurProxyPath = z + .string() + .min(1, 'path is required') + .refine( + (p) => + !p.split(/[/\\]/).some((seg) => seg === '..' || seg === '.') && + !p.includes('#') && + !/%(?:2[eEfF]|5[cC]|23)/.test(p), + { + message: + 'path must not contain ".." or "." segments, "#", or percent-encoded path/fragment characters', + } + ) + +export const SapConcurProxyRequestSchema = SapConcurAuthSchema.extend({ + path: SapConcurProxyPath, + method: SapConcurHttpMethod.default('GET'), + query: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).optional(), + body: z.unknown().optional(), + contentType: z.string().optional(), +}).superRefine((req, ctx) => { + if (req.grantType === 'password') { + if (!req.username) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ['username'], + message: 'username is required for password grant', + }) + } + if (!req.password) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + path: ['password'], + message: 'password is required for password grant', + }) + } + } +}) + +export type SapConcurProxyRequest = z.infer + +export const SapConcurUploadOperation = z.enum([ + 'upload_receipt_image', + 'create_quick_expense_with_image', +]) + +export const SapConcurUploadRequestSchema = SapConcurAuthSchema.extend({ + operation: SapConcurUploadOperation, + userId: z.string().min(1, 'userId is required'), + contextType: z.string().optional(), + receipt: FileInputSchema, + forwardId: z.string().max(40).optional(), + body: z.union([z.record(z.string(), z.unknown()), z.string()]).optional(), +}) + +export type SapConcurUploadRequest = z.infer + +const FORBIDDEN_HOSTS = new Set([ + 'localhost', + '0.0.0.0', + '127.0.0.1', + '169.254.169.254', + 'metadata.google.internal', + 'metadata', + '[::1]', + '[::]', + '[::ffff:127.0.0.1]', + '[fd00:ec2::254]', +]) + +function isPrivateIPv4(host: string): boolean { + const match = host.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/) + if (!match) return false + const octets = match.slice(1, 5).map(Number) as [number, number, number, number] + if (octets.some((o) => o < 0 || o > 255)) return false + const [a, b] = octets + if (a === 10) return true + if (a === 172 && b >= 16 && b <= 31) return true + if (a === 192 && b === 168) return true + if (a === 127) return true + if (a === 169 && b === 254) return true + if (a === 0) return true + return false +} + +function isPrivateOrLoopbackIPv6(host: string): boolean { + const stripped = host.startsWith('[') && host.endsWith(']') ? host.slice(1, -1) : host + const lower = stripped.toLowerCase() + if (lower === '::' || lower === '::1') return true + if (/^fc[0-9a-f]{2}:/.test(lower) || /^fd[0-9a-f]{2}:/.test(lower)) return true + if (lower.startsWith('fe80:')) return true + return false +} + +/** Validate a URL is https and not pointing to a private/loopback host. */ +export function assertSafeExternalUrl(rawUrl: string, label: string): URL { + let parsed: URL + try { + parsed = new URL(rawUrl) + } catch { + throw new Error(`${label} must be a valid URL`) + } + if (parsed.protocol !== 'https:') { + throw new Error(`${label} must use https://`) + } + const host = parsed.hostname.toLowerCase() + if (FORBIDDEN_HOSTS.has(host) || FORBIDDEN_HOSTS.has(`[${host}]`)) { + throw new Error(`${label} host is not allowed`) + } + if (isPrivateIPv4(host)) { + throw new Error(`${label} host is not allowed (private/loopback range)`) + } + if (isPrivateOrLoopbackIPv6(host)) { + throw new Error(`${label} host is not allowed (IPv6 private/loopback)`) + } + return parsed +} + +interface CachedToken { + accessToken: string + geolocation: string + expiresAt: number +} + +const TOKEN_CACHE = new Map() +const TOKEN_CACHE_MAX_ENTRIES = 500 +const TOKEN_SAFETY_WINDOW_MS = 60_000 +export const SAP_CONCUR_OUTBOUND_FETCH_TIMEOUT_MS = 30_000 + +function tokenCacheKey(req: SapConcurAuth): string { + const secretHash = createHash('sha256').update(req.clientSecret).digest('hex').slice(0, 16) + const userHash = req.username + ? createHash('sha256').update(req.username).digest('hex').slice(0, 12) + : '' + return `${req.datacenter}::${req.grantType}::${req.clientId}::${secretHash}::${userHash}` +} + +function rememberToken(key: string, token: CachedToken): void { + if (TOKEN_CACHE.has(key)) TOKEN_CACHE.delete(key) + TOKEN_CACHE.set(key, token) + while (TOKEN_CACHE.size > TOKEN_CACHE_MAX_ENTRIES) { + const oldestKey = TOKEN_CACHE.keys().next().value + if (oldestKey === undefined) break + TOKEN_CACHE.delete(oldestKey) + } +} + +function normalizeGeolocation(raw: string | undefined, fallback: string): string { + if (!raw) return `https://${fallback}` + const trimmed = raw.replace(/\/+$/, '') + if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) return trimmed + return `https://${trimmed}` +} + +/** + * Acquire a Concur access token, sharing a cache with the proxy route. + * Validates that the geolocation returned by Concur is a safe external URL. + */ +export async function fetchSapConcurAccessToken( + auth: SapConcurAuth, + requestId: string +): Promise<{ accessToken: string; geolocation: string }> { + if (auth.grantType === 'password') { + if (!auth.username) throw new Error('username is required for password grant') + if (!auth.password) throw new Error('password is required for password grant') + } + + const cacheKey = tokenCacheKey(auth) + const cached = TOKEN_CACHE.get(cacheKey) + if (cached && cached.expiresAt - TOKEN_SAFETY_WINDOW_MS > Date.now()) { + return { accessToken: cached.accessToken, geolocation: cached.geolocation } + } + + const tokenUrl = assertSafeExternalUrl( + `https://${auth.datacenter}/oauth2/v0/token`, + 'tokenUrl' + ).toString() + + const params = new URLSearchParams() + params.set('client_id', auth.clientId) + params.set('client_secret', auth.clientSecret) + params.set('grant_type', auth.grantType) + if (auth.grantType === 'password') { + params.set('username', auth.username ?? '') + params.set('password', auth.password ?? '') + if (auth.companyUuid) params.set('credtype', 'authtoken') + } + + const response = await secureFetchWithValidation( + tokenUrl, + { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Accept: 'application/json', + }, + body: params.toString(), + timeout: SAP_CONCUR_OUTBOUND_FETCH_TIMEOUT_MS, + }, + 'tokenUrl' + ) + + if (!response.ok) { + const text = await response.text().catch(() => '') + logger.warn(`[${requestId}] Concur token fetch failed (${response.status}): ${text}`) + throw new Error(`Concur token request failed: HTTP ${response.status}`) + } + + const data = (await response.json()) as { + access_token?: string + expires_in?: number + geolocation?: string + } + + if (!data.access_token) { + throw new Error('Concur token response missing access_token') + } + + const geolocation = normalizeGeolocation(data.geolocation, auth.datacenter) + const geolocationUrl = assertSafeExternalUrl(geolocation, 'geolocation') + if (!SAP_CONCUR_ALLOWED_DATACENTERS.has(geolocationUrl.hostname.toLowerCase())) { + throw new Error( + `Concur geolocation host is not in the allowed datacenter list: ${geolocationUrl.hostname}` + ) + } + + const expiresInMs = (data.expires_in ?? 3600) * 1000 + rememberToken(cacheKey, { + accessToken: data.access_token, + geolocation, + expiresAt: Date.now() + expiresInMs, + }) + return { accessToken: data.access_token, geolocation } +} + +/** Extract a meaningful error message from a Concur error response body. */ +export function extractSapConcurError(body: unknown, status: number): string { + if (body && typeof body === 'object') { + const obj = body as Record + if (typeof obj.error === 'string' && obj.error.length > 0) { + const desc = typeof obj.error_description === 'string' ? `: ${obj.error_description}` : '' + return `${obj.error}${desc}` + } + if (typeof obj.message === 'string' && obj.message.length > 0) { + return obj.message + } + const errors = obj.errors + if (Array.isArray(errors) && errors.length > 0) { + return errors + .map((e) => { + if (e && typeof e === 'object') { + const eo = e as Record + const code = typeof eo.errorCode === 'string' ? `[${eo.errorCode}] ` : '' + const msg = typeof eo.errorMessage === 'string' ? eo.errorMessage : '' + return `${code}${msg}`.trim() + } + return String(e) + }) + .filter(Boolean) + .join('; ') + } + } + if (typeof body === 'string' && body.length > 0) return body + return `Concur request failed with HTTP ${status}` +} diff --git a/apps/sim/app/api/tools/sap_concur/upload/route.ts b/apps/sim/app/api/tools/sap_concur/upload/route.ts new file mode 100644 index 00000000000..81885d9a29f --- /dev/null +++ b/apps/sim/app/api/tools/sap_concur/upload/route.ts @@ -0,0 +1,279 @@ +import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { getValidationErrorMessage, isZodError } from '@/lib/api/server' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server' +import { generateRequestId } from '@/lib/core/utils/request' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { processFilesToUserFiles, type RawFileInput } from '@/lib/uploads/utils/file-utils' +import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' +import { + assertSafeExternalUrl, + extractSapConcurError, + fetchSapConcurAccessToken, + SAP_CONCUR_OUTBOUND_FETCH_TIMEOUT_MS, + type SapConcurUploadRequest, + SapConcurUploadRequestSchema, +} from '@/app/api/tools/sap_concur/shared' + +export const dynamic = 'force-dynamic' + +const logger = createLogger('SapConcurUploadAPI') + +type UploadRequest = SapConcurUploadRequest + +const RECEIPT_ALLOWED_MIME_TYPES = new Set([ + 'application/pdf', + 'image/png', + 'image/jpeg', + 'image/jpg', + 'image/gif', + 'image/tiff', +]) + +const QUICK_EXPENSE_ALLOWED_MIME_TYPES = new Set([ + 'application/pdf', + 'image/png', + 'image/jpeg', + 'image/jpg', + 'image/tiff', +]) + +const ALLOWED_MIME_TYPES = RECEIPT_ALLOWED_MIME_TYPES + +function inferMimeType(name: string, declared?: string): string { + if (declared && ALLOWED_MIME_TYPES.has(declared.toLowerCase())) { + return declared.toLowerCase() === 'image/jpg' ? 'image/jpeg' : declared.toLowerCase() + } + const lower = name.toLowerCase() + if (lower.endsWith('.pdf')) return 'application/pdf' + if (lower.endsWith('.png')) return 'image/png' + if (lower.endsWith('.jpg') || lower.endsWith('.jpeg')) return 'image/jpeg' + if (lower.endsWith('.gif')) return 'image/gif' + if (lower.endsWith('.tif') || lower.endsWith('.tiff')) return 'image/tiff' + return 'application/octet-stream' +} + +function stringifyMaybeJson(value: unknown): string { + if (typeof value === 'string') return value + return JSON.stringify(value ?? {}) +} + +interface UploadInvocation { + status: number + body: unknown +} + +async function postMultipart( + url: string, + accessToken: string, + formData: FormData, + companyUuid: string | undefined, + extraHeaders?: Record +): Promise { + const headers: Record = { + Authorization: `Bearer ${accessToken}`, + Accept: 'application/json', + ...(extraHeaders ?? {}), + } + if (companyUuid) headers['concur-correlationid'] = companyUuid + + // Serialize FormData (with auto-generated multipart boundary) to a Buffer so we can + // route through secureFetchWithValidation (which doesn't support FormData bodies directly). + const serialized = new Request('http://localhost/internal-multipart-serializer', { + method: 'POST', + body: formData, + }) + const contentType = serialized.headers.get('content-type') + if (contentType) headers['Content-Type'] = contentType + const bodyBuffer = Buffer.from(await serialized.arrayBuffer()) + + const response = await secureFetchWithValidation( + url, + { + method: 'POST', + headers, + body: bodyBuffer, + timeout: SAP_CONCUR_OUTBOUND_FETCH_TIMEOUT_MS, + }, + 'apiUrl' + ) + + const raw = await response.text() + let parsed: unknown = null + if (raw.length > 0) { + try { + parsed = JSON.parse(raw) + } catch { + parsed = raw + } + } + // Surface Location/Link headers for receipt endpoints that return 202 with no body. + if ( + parsed === null || + (typeof parsed === 'object' && parsed !== null && Object.keys(parsed).length === 0) + ) { + const location = response.headers.get('Location') + const link = response.headers.get('Link') + if (location || link) { + parsed = { location, link } + } + } + return { status: response.status, body: parsed } +} + +async function handleUploadReceiptImage( + req: UploadRequest, + fileBuffer: Buffer, + fileName: string, + mimeType: string, + accessToken: string, + geolocation: string +): Promise { + const url = assertSafeExternalUrl( + `${geolocation.replace(/\/+$/, '')}/receipts/v4/users/${encodeURIComponent(req.userId)}/image-only-receipts`, + 'apiUrl' + ).toString() + + const formData = new FormData() + formData.append('image', new Blob([new Uint8Array(fileBuffer)], { type: mimeType }), fileName) + + const extraHeaders: Record | undefined = req.forwardId + ? { 'concur-forwardid': req.forwardId } + : undefined + + return postMultipart(url, accessToken, formData, req.companyUuid, extraHeaders) +} + +async function handleCreateQuickExpenseWithImage( + req: UploadRequest, + fileBuffer: Buffer, + fileName: string, + mimeType: string, + accessToken: string, + geolocation: string +): Promise { + const contextType = req.contextType?.trim() || 'TRAVELER' + const url = assertSafeExternalUrl( + `${geolocation.replace(/\/+$/, '')}/quickexpense/v4/users/${encodeURIComponent( + req.userId + )}/context/${encodeURIComponent(contextType)}/quickexpenses/image`, + 'apiUrl' + ).toString() + + const quickExpenseRequest = stringifyMaybeJson(req.body ?? {}) + + const formData = new FormData() + formData.append('quickExpenseRequest', quickExpenseRequest) + formData.append( + 'fileContent', + new Blob([new Uint8Array(fileBuffer)], { type: mimeType }), + fileName + ) + + return postMultipart(url, accessToken, formData, req.companyUuid) +} + +export const POST = withRouteHandler(async (request: NextRequest) => { + const requestId = generateRequestId() + + try { + const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) + if (!authResult.success) { + logger.warn(`[${requestId}] Unauthorized Concur upload request: ${authResult.error}`) + return NextResponse.json( + { success: false, error: authResult.error || 'Authentication required' }, + { status: 401 } + ) + } + + // boundary-raw-json: internal upload envelope validated by SapConcurUploadRequestSchema below; not a public boundary + const json = await request.json() + const uploadReq = SapConcurUploadRequestSchema.parse(json) + + const userFiles = processFilesToUserFiles( + [uploadReq.receipt as RawFileInput], + requestId, + logger + ) + if (userFiles.length === 0) { + return NextResponse.json( + { success: false, error: 'Invalid receipt file input' }, + { status: 400 } + ) + } + const userFile = userFiles[0] + const fileBuffer = await downloadFileFromStorage(userFile, requestId, logger) + const fileName = userFile.name + const mimeType = inferMimeType(fileName, userFile.type) + + const allowedForOperation = + uploadReq.operation === 'create_quick_expense_with_image' + ? QUICK_EXPENSE_ALLOWED_MIME_TYPES + : RECEIPT_ALLOWED_MIME_TYPES + if (!allowedForOperation.has(mimeType)) { + const allowedLabel = + uploadReq.operation === 'create_quick_expense_with_image' + ? 'pdf, png, jpeg, tiff' + : 'pdf, png, jpeg, gif, tiff' + return NextResponse.json( + { + success: false, + error: `Unsupported receipt mime type: ${mimeType}. Allowed: ${allowedLabel}`, + }, + { status: 400 } + ) + } + + const { accessToken, geolocation } = await fetchSapConcurAccessToken(uploadReq, requestId) + + let invocation: UploadInvocation + if (uploadReq.operation === 'upload_receipt_image') { + invocation = await handleUploadReceiptImage( + uploadReq, + fileBuffer, + fileName, + mimeType, + accessToken, + geolocation + ) + } else { + invocation = await handleCreateQuickExpenseWithImage( + uploadReq, + fileBuffer, + fileName, + mimeType, + accessToken, + geolocation + ) + } + + if (invocation.status >= 200 && invocation.status < 300) { + const data = invocation.status === 204 ? null : invocation.body + logger.info( + `[${requestId}] Concur ${uploadReq.operation} succeeded: HTTP ${invocation.status}` + ) + return NextResponse.json({ success: true, output: { status: invocation.status, data } }) + } + + const message = extractSapConcurError(invocation.body, invocation.status) + logger.warn( + `[${requestId}] Concur upload error (${invocation.status}) ${uploadReq.operation}: ${message}` + ) + return NextResponse.json( + { success: false, error: message, status: invocation.status }, + { status: invocation.status } + ) + } catch (error) { + if (isZodError(error)) { + logger.warn(`[${requestId}] Validation error:`, error.issues) + return NextResponse.json( + { success: false, error: getValidationErrorMessage(error, 'Validation failed') }, + { status: 400 } + ) + } + logger.error(`[${requestId}] Unexpected Concur upload error:`, error) + return NextResponse.json({ success: false, error: toError(error).message }, { status: 500 }) + } +}) diff --git a/apps/sim/app/api/tools/sap_s4hana/proxy/route.ts b/apps/sim/app/api/tools/sap_s4hana/proxy/route.ts index d3414a48af1..bee4a8b84aa 100644 --- a/apps/sim/app/api/tools/sap_s4hana/proxy/route.ts +++ b/apps/sim/app/api/tools/sap_s4hana/proxy/route.ts @@ -9,6 +9,10 @@ import { } from '@/lib/api/contracts/tools/sap' import { getValidationErrorMessage, parseRequest } from '@/lib/api/server' import { checkInternalAuth } from '@/lib/auth/hybrid' +import { + type SecureFetchResponse, + secureFetchWithValidation, +} from '@/lib/core/security/input-validation.server' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' @@ -65,16 +69,20 @@ async function fetchAccessToken(req: ProxyRequest, requestId: string): Promise '') @@ -104,12 +112,9 @@ interface CsrfBundle { cookie: string } -function joinSetCookies(headers: Headers): string { - const cookies = - typeof (headers as { getSetCookie?: () => string[] }).getSetCookie === 'function' - ? (headers as { getSetCookie: () => string[] }).getSetCookie() - : (headers.get('set-cookie') ?? '').split(/,\s*(?=[^=,;\s]+=)/) - return cookies +function joinSetCookies(response: SecureFetchResponse): string { + return response.headers + .getSetCookie() .map((c) => c.split(';')[0]?.trim()) .filter(Boolean) .join('; ') @@ -129,15 +134,19 @@ async function fetchCsrf( requestId: string ): Promise { const url = buildOdataUrl(req, '/$metadata') - const response = await fetch(url, { - method: 'GET', - headers: { - Authorization: buildAuthHeader(req, accessToken), - Accept: 'application/xml', - 'X-CSRF-Token': 'Fetch', + const response = await secureFetchWithValidation( + url, + { + method: 'GET', + headers: { + Authorization: buildAuthHeader(req, accessToken), + Accept: 'application/xml', + 'X-CSRF-Token': 'Fetch', + }, + timeout: OUTBOUND_FETCH_TIMEOUT_MS, }, - signal: AbortSignal.timeout(OUTBOUND_FETCH_TIMEOUT_MS), - }) + 'baseUrl' + ) if (!response.ok) { const text = await response.text().catch(() => '') @@ -146,7 +155,7 @@ async function fetchCsrf( } const token = response.headers.get('x-csrf-token') - const cookie = joinSetCookies(response.headers) + const cookie = joinSetCookies(response) if (!token) return null return { token, cookie } } @@ -217,12 +226,16 @@ async function callOdata( if (csrf.cookie) headers.Cookie = csrf.cookie } - const response = await fetch(url, { - method: req.method, - headers, - body: hasBody ? JSON.stringify(req.body) : undefined, - signal: AbortSignal.timeout(OUTBOUND_FETCH_TIMEOUT_MS), - }) + const response = await secureFetchWithValidation( + url, + { + method: req.method, + headers, + body: hasBody ? JSON.stringify(req.body) : undefined, + timeout: OUTBOUND_FETCH_TIMEOUT_MS, + }, + 'baseUrl' + ) const raw = await response.text() let parsed: unknown = null diff --git a/apps/sim/app/api/tools/sharepoint/upload/route.ts b/apps/sim/app/api/tools/sharepoint/upload/route.ts index 20a735a1de9..556de6d4225 100644 --- a/apps/sim/app/api/tools/sharepoint/upload/route.ts +++ b/apps/sim/app/api/tools/sharepoint/upload/route.ts @@ -1,4 +1,5 @@ import { createLogger } from '@sim/logger' +import { toError } from '@sim/utils/errors' import { type NextRequest, NextResponse } from 'next/server' import { sharepointUploadContract } from '@/lib/api/contracts/tools/microsoft' import { parseRequest } from '@/lib/api/server' @@ -9,10 +10,12 @@ import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils' import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' import type { MicrosoftGraphDriveItem } from '@/tools/onedrive/types' +import type { SharepointSkippedFile, SharepointUploadError } from '@/tools/sharepoint/types' export const dynamic = 'force-dynamic' const logger = createLogger('SharepointUploadAPI') +const MAX_SHAREPOINT_UPLOAD_BYTES = 250 * 1024 * 1024 export const POST = withRouteHandler(async (request: NextRequest) => { const requestId = generateRequestId() @@ -72,42 +75,11 @@ export const POST = withRouteHandler(async (request: NextRequest) => { ) } - let effectiveDriveId = validatedData.driveId - if (!effectiveDriveId) { - logger.info(`[${requestId}] No driveId provided, fetching default drive for site`) - const driveUrl = `https://graph.microsoft.com/v1.0/sites/${validatedData.siteId}/drive` - const driveResponse = await secureFetchWithValidation( - driveUrl, - { - method: 'GET', - headers: { - Authorization: `Bearer ${validatedData.accessToken}`, - Accept: 'application/json', - }, - }, - 'driveUrl' - ) - - if (!driveResponse.ok) { - const errorData = (await driveResponse.json().catch(() => ({}))) as { - error?: { message?: string } - } - logger.error(`[${requestId}] Failed to get default drive:`, errorData) - return NextResponse.json( - { - success: false, - error: errorData.error?.message || 'Failed to get default document library', - }, - { status: driveResponse.status } - ) - } - - const driveData = (await driveResponse.json()) as { id: string } - effectiveDriveId = driveData.id - logger.info(`[${requestId}] Using default drive: ${effectiveDriveId}`) - } - - const uploadedFiles: any[] = [] + const siteId = validatedData.siteId.trim() || 'root' + const driveId = validatedData.driveId?.trim() || null + const uploadedFiles: MicrosoftGraphDriveItem[] = [] + const skippedFiles: SharepointSkippedFile[] = [] + const errors: SharepointUploadError[] = [] for (const userFile of userFiles) { logger.info(`[${requestId}] Uploading file: ${userFile.name}`) @@ -119,10 +91,16 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const fileSizeMB = buffer.length / (1024 * 1024) - if (fileSizeMB > 250) { + if (buffer.length > MAX_SHAREPOINT_UPLOAD_BYTES) { logger.warn( `[${requestId}] File ${fileName} is ${fileSizeMB.toFixed(2)}MB, exceeds 250MB limit` ) + skippedFiles.push({ + name: fileName, + size: buffer.length, + limit: MAX_SHAREPOINT_UPLOAD_BYTES, + reason: 'File exceeds the 250 MB Microsoft Graph small upload limit', + }) continue } @@ -142,7 +120,9 @@ export const POST = withRouteHandler(async (request: NextRequest) => { .map((segment) => (segment ? encodeURIComponent(segment) : '')) .join('/') - const uploadUrl = `https://graph.microsoft.com/v1.0/sites/${validatedData.siteId}/drives/${effectiveDriveId}/root:${encodedPath}:/content` + const uploadUrl = driveId + ? `https://graph.microsoft.com/v1.0/drives/${driveId}/root:${encodedPath}:/content` + : `https://graph.microsoft.com/v1.0/sites/${siteId}/drive/root:${encodedPath}:/content` logger.info(`[${requestId}] Uploading to: ${uploadUrl}`) @@ -185,13 +165,12 @@ export const POST = withRouteHandler(async (request: NextRequest) => { error?: { message?: string } } logger.error(`[${requestId}] Failed to replace file ${fileName}:`, replaceErrorData) - return NextResponse.json( - { - success: false, - error: replaceErrorData.error?.message || `Failed to replace file: ${fileName}`, - }, - { status: replaceResponse.status } - ) + errors.push({ + name: fileName, + status: replaceResponse.status, + error: replaceErrorData.error?.message || `Failed to replace file: ${fileName}`, + }) + continue } const replaceData = (await replaceResponse.json()) as { @@ -215,15 +194,14 @@ export const POST = withRouteHandler(async (request: NextRequest) => { continue } - return NextResponse.json( - { - success: false, - error: - (errorData as { error?: { message?: string } }).error?.message || - `Failed to upload file: ${fileName}`, - }, - { status: uploadResponse.status } - ) + errors.push({ + name: fileName, + status: uploadResponse.status, + error: + (errorData as { error?: { message?: string } }).error?.message || + `Failed to upload file: ${fileName}`, + }) + continue } const uploadData = (await uploadResponse.json()) as MicrosoftGraphDriveItem @@ -240,22 +218,33 @@ export const POST = withRouteHandler(async (request: NextRequest) => { } if (uploadedFiles.length === 0) { - return NextResponse.json( - { - success: false, - error: 'No files were uploaded successfully', + return NextResponse.json({ + success: false, + error: 'No files were uploaded successfully', + output: { + uploadedFiles, + fileCount: 0, + skippedFiles, + skippedCount: skippedFiles.length, + errors, }, - { status: 400 } - ) + }) } - logger.info(`[${requestId}] Successfully uploaded ${uploadedFiles.length} file(s)`) + logger.info(`[${requestId}] Completed SharePoint upload`, { + uploadedCount: uploadedFiles.length, + skippedCount: skippedFiles.length, + errorCount: errors.length, + }) return NextResponse.json({ success: true, output: { uploadedFiles, fileCount: uploadedFiles.length, + skippedFiles, + skippedCount: skippedFiles.length, + errors, }, }) } catch (error) { @@ -263,7 +252,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { return NextResponse.json( { success: false, - error: error instanceof Error ? error.message : 'Unknown error occurred', + error: toError(error).message, }, { status: 500 } ) diff --git a/apps/sim/app/api/tools/workday/assign-onboarding/route.ts b/apps/sim/app/api/tools/workday/assign-onboarding/route.ts index 618a3bd5d36..044dc7c662c 100644 --- a/apps/sim/app/api/tools/workday/assign-onboarding/route.ts +++ b/apps/sim/app/api/tools/workday/assign-onboarding/route.ts @@ -36,7 +36,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { Onboarding_Plan_Assignment_Data: { Onboarding_Plan_Reference: wdRef('Onboarding_Plan_ID', data.onboardingPlanId), Person_Reference: wdRef('WID', data.workerId), - Action_Event_Reference: wdRef('Background_Check_ID', data.actionEventId), + Action_Event_Reference: wdRef('WID', data.actionEventId), Assignment_Effective_Moment: new Date().toISOString(), Active: true, }, diff --git a/apps/sim/app/api/tools/workday/create-prehire/route.ts b/apps/sim/app/api/tools/workday/create-prehire/route.ts index c7937807040..fae29cdd054 100644 --- a/apps/sim/app/api/tools/workday/create-prehire/route.ts +++ b/apps/sim/app/api/tools/workday/create-prehire/route.ts @@ -48,7 +48,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { const client = await createWorkdaySoapClient( data.tenantUrl, data.tenant, - 'staffing', + 'recruiting', data.username, data.password ) diff --git a/apps/sim/app/api/v1/logs/[id]/route.ts b/apps/sim/app/api/v1/logs/[id]/route.ts index 81e876c25f5..c32acfd444c 100644 --- a/apps/sim/app/api/v1/logs/[id]/route.ts +++ b/apps/sim/app/api/v1/logs/[id]/route.ts @@ -1,14 +1,18 @@ import { db } from '@sim/db' -import { permissions, workflow, workflowExecutionLogs } from '@sim/db/schema' +import { workflow, workflowExecutionLogs } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { generateId } from '@sim/utils/id' -import { and, eq } from 'drizzle-orm' +import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { v1GetLogContract } from '@/lib/api/contracts/v1/logs' import { parseRequest } from '@/lib/api/server' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { createApiResponse, getUserLimits } from '@/app/api/v1/logs/meta' -import { checkRateLimit, createRateLimitResponse } from '@/app/api/v1/middleware' +import { + checkRateLimit, + createRateLimitResponse, + validateWorkspaceAccess, +} from '@/app/api/v1/middleware' const logger = createLogger('V1LogDetailsAPI') @@ -37,6 +41,7 @@ export const GET = withRouteHandler( .select({ id: workflowExecutionLogs.id, workflowId: workflowExecutionLogs.workflowId, + workspaceId: workflowExecutionLogs.workspaceId, executionId: workflowExecutionLogs.executionId, stateSnapshotId: workflowExecutionLogs.stateSnapshotId, level: workflowExecutionLogs.level, @@ -59,14 +64,6 @@ export const GET = withRouteHandler( }) .from(workflowExecutionLogs) .leftJoin(workflow, eq(workflowExecutionLogs.workflowId, workflow.id)) - .innerJoin( - permissions, - and( - eq(permissions.entityType, 'workspace'), - eq(permissions.entityId, workflowExecutionLogs.workspaceId), - eq(permissions.userId, userId) - ) - ) .where(eq(workflowExecutionLogs.id, id)) .limit(1) @@ -75,6 +72,11 @@ export const GET = withRouteHandler( return NextResponse.json({ error: 'Log not found' }, { status: 404 }) } + const accessError = await validateWorkspaceAccess(rateLimit, userId, log.workspaceId) + if (accessError) { + return NextResponse.json({ error: 'Log not found' }, { status: 404 }) + } + const workflowSummary = { id: log.workflowId, name: log.workflowName || 'Deleted Workflow', diff --git a/apps/sim/app/api/v1/logs/executions/[executionId]/route.ts b/apps/sim/app/api/v1/logs/executions/[executionId]/route.ts index 1f373c2e3f7..e7503ecb071 100644 --- a/apps/sim/app/api/v1/logs/executions/[executionId]/route.ts +++ b/apps/sim/app/api/v1/logs/executions/[executionId]/route.ts @@ -1,13 +1,17 @@ import { db } from '@sim/db' -import { permissions, workflowExecutionLogs, workflowExecutionSnapshots } from '@sim/db/schema' +import { workflowExecutionLogs, workflowExecutionSnapshots } from '@sim/db/schema' import { createLogger } from '@sim/logger' -import { and, eq } from 'drizzle-orm' +import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { v1GetExecutionContract } from '@/lib/api/contracts/v1/logs' import { parseRequest } from '@/lib/api/server' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { createApiResponse, getUserLimits } from '@/app/api/v1/logs/meta' -import { checkRateLimit, createRateLimitResponse } from '@/app/api/v1/middleware' +import { + checkRateLimit, + createRateLimitResponse, + validateWorkspaceAccess, +} from '@/app/api/v1/middleware' const logger = createLogger('V1ExecutionAPI') @@ -31,18 +35,8 @@ export const GET = withRouteHandler( logger.debug(`Fetching execution data for: ${executionId}`) const rows = await db - .select({ - log: workflowExecutionLogs, - }) + .select() .from(workflowExecutionLogs) - .innerJoin( - permissions, - and( - eq(permissions.entityType, 'workspace'), - eq(permissions.entityId, workflowExecutionLogs.workspaceId), - eq(permissions.userId, userId) - ) - ) .where(eq(workflowExecutionLogs.executionId, executionId)) .limit(1) @@ -50,7 +44,12 @@ export const GET = withRouteHandler( return NextResponse.json({ error: 'Workflow execution not found' }, { status: 404 }) } - const { log: workflowLog } = rows[0] + const workflowLog = rows[0] + + const accessError = await validateWorkspaceAccess(rateLimit, userId, workflowLog.workspaceId) + if (accessError) { + return NextResponse.json({ error: 'Workflow execution not found' }, { status: 404 }) + } const [snapshot] = await db .select() diff --git a/apps/sim/app/api/v1/logs/route.ts b/apps/sim/app/api/v1/logs/route.ts index 53513407c60..0f8f7b31b82 100644 --- a/apps/sim/app/api/v1/logs/route.ts +++ b/apps/sim/app/api/v1/logs/route.ts @@ -9,7 +9,11 @@ import { getValidationErrorMessage, parseRequest } from '@/lib/api/server' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { buildLogFilters, getOrderBy } from '@/app/api/v1/logs/filters' import { createApiResponse, getUserLimits } from '@/app/api/v1/logs/meta' -import { checkRateLimit, createRateLimitResponse } from '@/app/api/v1/middleware' +import { + checkRateLimit, + checkWorkspaceScope, + createRateLimitResponse, +} from '@/app/api/v1/middleware' const logger = createLogger('V1LogsAPI') @@ -62,6 +66,9 @@ export const GET = withRouteHandler(async (request: NextRequest) => { const params = parsed.data.query + const scopeError = checkWorkspaceScope(rateLimit, params.workspaceId) + if (scopeError) return scopeError + logger.info(`[${requestId}] Fetching logs for workspace ${params.workspaceId}`, { userId, filters: { diff --git a/apps/sim/app/api/v1/workflows/[id]/route.ts b/apps/sim/app/api/v1/workflows/[id]/route.ts index 309eb3b96de..76d7393870f 100644 --- a/apps/sim/app/api/v1/workflows/[id]/route.ts +++ b/apps/sim/app/api/v1/workflows/[id]/route.ts @@ -9,9 +9,12 @@ import { v1GetWorkflowContract } from '@/lib/api/contracts/v1/workflows' import { parseRequest } from '@/lib/api/server' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { extractInputFieldsFromBlocks } from '@/lib/workflows/input-format' -import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' import { createApiResponse, getUserLimits } from '@/app/api/v1/logs/meta' -import { checkRateLimit, createRateLimitResponse } from '@/app/api/v1/middleware' +import { + checkRateLimit, + createRateLimitResponse, + validateWorkspaceAccess, +} from '@/app/api/v1/middleware' const logger = createLogger('V1WorkflowDetailsAPI') @@ -43,13 +46,13 @@ export const GET = withRouteHandler( return NextResponse.json({ error: 'Workflow not found' }, { status: 404 }) } - const permission = await getUserEntityPermissions( + const accessError = await validateWorkspaceAccess( + rateLimit, userId, - 'workspace', workflowData.workspaceId! ) - if (!permission) { - return NextResponse.json({ error: 'Access denied' }, { status: 403 }) + if (accessError) { + return NextResponse.json({ error: 'Workflow not found' }, { status: 404 }) } const blockRows = await db diff --git a/apps/sim/app/api/v1/workflows/route.ts b/apps/sim/app/api/v1/workflows/route.ts index cbb3e7ffc45..707a6a3de43 100644 --- a/apps/sim/app/api/v1/workflows/route.ts +++ b/apps/sim/app/api/v1/workflows/route.ts @@ -7,9 +7,12 @@ import { type NextRequest, NextResponse } from 'next/server' import { v1ListWorkflowsContract } from '@/lib/api/contracts/v1/workflows' import { getValidationErrorMessage, parseRequest } from '@/lib/api/server' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' -import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' import { createApiResponse, getUserLimits } from '@/app/api/v1/logs/meta' -import { checkRateLimit, createRateLimitResponse } from '@/app/api/v1/middleware' +import { + checkRateLimit, + createRateLimitResponse, + validateWorkspaceAccess, +} from '@/app/api/v1/middleware' const logger = createLogger('V1WorkflowsAPI') @@ -71,10 +74,8 @@ export const GET = withRouteHandler(async (request: NextRequest) => { }, }) - const permission = await getUserEntityPermissions(userId, 'workspace', params.workspaceId) - if (!permission) { - return NextResponse.json({ error: 'Access denied' }, { status: 403 }) - } + const accessError = await validateWorkspaceAccess(rateLimit, userId, params.workspaceId) + if (accessError) return accessError const conditions = [eq(workflow.workspaceId, params.workspaceId), isNull(workflow.archivedAt)] diff --git a/apps/sim/app/chat/[identifier]/office-embed-init.tsx b/apps/sim/app/chat/[identifier]/office-embed-init.tsx new file mode 100644 index 00000000000..02729d65274 --- /dev/null +++ b/apps/sim/app/chat/[identifier]/office-embed-init.tsx @@ -0,0 +1,50 @@ +'use client' + +import Script from 'next/script' + +declare global { + interface Window { + Office?: { + onReady: () => Promise<{ host: string | null; platform: string | null }> + } + } +} + +/** + * Office.js nullifies window.history.replaceState and pushState (a legacy + * IE10 workaround inside the library) which breaks Next.js's client-side + * router. Cache the originals at module load — before