Setu Recipes: reusable digital workers across every app.Browse templates

Integrations / SAP Concur

SAP Concur

Travel and expense: expense reports (Expense Reports v3 and v4), report expenses and entries, users from the Identity v4.1 service, Concur Request travel requests, and writes to create a report, add an expense, submit, approve or send back. Each Concur account uses its own API host (data centre), taken from the geolocation in the sign-in reply. The company must register an OAuth application (client id and secret) with SAP Concur (App Management / SAP App Center) and enable the scopes; v4 calls need the user's Concur UUID. Request shapes follow developer.concur.com; Setu has not verified them against a live Concur tenant.

15 actions OAuth 2 10 read5 write Calls glz.api.concursolutions.com

Connect

TypeScript
const conn = await setu.connections.initiate({ userId: "user_1", toolkit: "sap_concur" });
console.log(conn.connectUrl); // show this to your user
await conn.waitUntilActive();

Permissions requested

ScopeWhy it is requested
EXPRPTRead expense reports and entries and add expenses (Expense v3)
expense.report.readRead expense reports and their expenses
expense.report.readwriteCreate reports, and submit them
expense.report.workflowstatus.writeApprove or send back expense reports
user.readLook up the Concur user id needed for expense report calls
identity.user.core.readRead user names and emails from the Identity service
travelrequest.writeRead and update Concur Request travel requests

Actions

Add expense to a report (v3)writesap_concur.add_expenseAdd an expense entry to an unsubmitted report with Expense Entries v3 (POST /api/v3.0/expense/entries). Required: report_id, expense_type_code and payment_type_id (valid codes come from the report's expense group configuration), transaction_date (yyyy-MM-dd), transaction_amount and transaction_currency_code (ISO 4217). Not allowed on a report that has already been in a workflow. Not for mileage (company/personal car) types. Returns the new entry's ID and URI.

Needs scope: EXPRPT

InputTypeDescription
report_idstringRequired
expense_type_codestringRequiredCode of an expense type active for the report's policy.
payment_type_idstringRequiredID of a payment type active for the report's expense group.
transaction_datestringRequiredyyyy-MM-dd
transaction_amountnumberRequiredAmount in the transaction currency.
transaction_currency_codestringRequired3-letter ISO 4217 code, e.g. USD.
descriptionstringOptional
commentstringOptional
userstringOptionalLogin id of the report owner (admin role needed).
TypeScript
await setu.execute({
  userId: "user_1",
  action: "sap_concur.add_expense",
  input: {
    "report_id": "<report_id>",
    "expense_type_code": "<expense_type_code>",
    "payment_type_id": "<payment_type_id>",
    "transaction_date": "<transaction_date>",
    "transaction_amount": 1,
    "transaction_currency_code": "<transaction_currency_code>"
  },
});
Approve expense reportwritesap_concur.approve_expense_reportApprove a submitted expense report as the signed-in approver (Workflows v4, PATCH .../context/MANAGER/reports/{reportId}/approve): advances it to the next workflow step. If any expense on the report was rejected, expense_rejected_comment is required. Approving authorises payment: only do it when the person explicitly asks. Returns nothing on success (HTTP 204).

Needs scope: expense.report.workflowstatus.write

InputTypeDescription
user_idstringRequiredThe approver's own Concur user UUID.
report_idstringRequired
commentstringOptionalApprover comment.
expense_rejected_commentstringOptionalWhy some expenses were rejected; required if the report has rejected expenses.
TypeScript
await setu.execute({
  userId: "user_1",
  action: "sap_concur.approve_expense_report",
  input: {
    "user_id": "<user_id>",
    "report_id": "<report_id>"
  },
});
Create expense reportwritesap_concur.create_expense_reportCreate a new, unsubmitted expense report for a user (Reports v4). Context TRAVELER creates it for the signed-in owner, PROXY for a delegate acting for them. Returns the new report's uri; the report id is its last path segment. Dates are yyyy-MM-dd. policyId is optional (the user's default policy applies otherwise).

Needs scopes: expense.report.readwrite, user.read

InputTypeDescription
user_idstringRequiredThe Concur user's UUID (the 'sub' of the sign-in id_token, or from the Identity API / the user's profile). Ask the person; do not guess.
context_typestringOptionalTRAVELER (default) or PROXY.
namestringRequiredReport name.
business_purposestringOptional
commentstringOptional
country_codestringOptionalISO 3166-1 country code, e.g. US.
country_sub_division_codestringOptionalISO 3166-2 code, e.g. US-WA.
policy_idstringOptional
report_datestringOptionalyyyy-MM-dd
start_datestringOptionalyyyy-MM-dd
end_datestringOptionalyyyy-MM-dd
TypeScript
await setu.execute({
  userId: "user_1",
  action: "sap_concur.create_expense_report",
  input: {
    "user_id": "<user_id>",
    "name": "<name>"
  },
});
Get an expense (v4)readsap_concur.get_expenseGet one expense (line item) of a report (Expenses v4) with its full detail: expense type, amounts, exchange rate, location, vendor, payment type, exceptions, receipt requirements. Context TRAVELER, MANAGER or PROXY.

Needs scopes: expense.report.read, user.read

InputTypeDescription
user_idstringRequiredThe Concur user's UUID (the 'sub' of the sign-in id_token, or from the Identity API / the user's profile). Ask the person; do not guess.
context_typestringRequiredThe caller's role for the report.
report_idstringRequired
expense_idstringRequiredThe expenseId from list_report_expenses (or ExpenseID of a v3 entry).
TypeScript
await setu.execute({
  userId: "user_1",
  action: "sap_concur.get_expense",
  input: {
    "user_id": "<user_id>",
    "context_type": "<context_type>",
    "report_id": "<report_id>",
    "expense_id": "<expense_id>"
  },
});
Get an expense entry (v3)readsap_concur.get_expense_entryGet one expense entry by id (Expense Entries v3), including expense type, amounts, dates, description, vendor and flags such as HasAttendees and HasImage.

Needs scope: EXPRPT

InputTypeDescription
entry_idstringRequiredThe entry's ID.
userstringOptionalLogin id of the entry's owner (admin role needed).
TypeScript
await setu.execute({
  userId: "user_1",
  action: "sap_concur.get_expense_entry",
  input: {
    "entry_id": "<entry_id>"
  },
});
Get expense report (v4)readsap_concur.get_expense_reportGet one expense report header (Reports v4): name, approval and payment status, totals, dates, policy, business purpose. Needs the owner's Concur user UUID and the report id (the ID from list_expense_reports). Context TRAVELER for the owner's own report, MANAGER for an approver, PROCESSOR for a processor, PROXY for a delegate.

Needs scopes: expense.report.read, user.read

InputTypeDescription
user_idstringRequiredThe Concur user's UUID (the 'sub' of the sign-in id_token, or from the Identity API / the user's profile). Ask the person; do not guess.
context_typestringRequiredThe caller's role for the report.
report_idstringRequiredThe report id.
TypeScript
await setu.execute({
  userId: "user_1",
  action: "sap_concur.get_expense_report",
  input: {
    "user_id": "<user_id>",
    "context_type": "<context_type>",
    "report_id": "<report_id>"
  },
});
Get a travel requestreadsap_concur.get_travel_requestGet the content of one Concur travel request by its UUID (Travel Request v4): status, owner, approver, dates, business purpose, totals, custom fields and links to segments, expected expenses and comments.

Needs scope: travelrequest.write

InputTypeDescription
request_idstringRequiredThe request's id (UUID-like, from list_travel_requests).
TypeScript
await setu.execute({
  userId: "user_1",
  action: "sap_concur.get_travel_request",
  input: {
    "request_id": "<request_id>"
  },
});
Get a user (identity v4.1)readsap_concur.get_userGet one user's identity profile by Concur user UUID from the SCIM Identity v4.1 service (name, emails, locale, active). Concur documents this API for company-level access tokens only, so it may be refused for a normal signed-in user.

Needs scope: identity.user.core.read

InputTypeDescription
user_idstringRequiredThe user's UUID.
attributesstringOptionalComma-separated attributes to return.
TypeScript
await setu.execute({
  userId: "user_1",
  action: "sap_concur.get_user",
  input: {
    "user_id": "<user_id>"
  },
});
List expense entries (v3)readsap_concur.list_expense_entriesList expense entries (line items, Expense Entries v3), optionally for one report (report_id) or filtered by expense type, payment type, billable, attendees or VAT. Returns entries with their Entry fields and next, the address of the next page. user (login id) needs the Web Services Admin role.

Needs scope: EXPRPT

InputTypeDescription
report_idstringOptionalOnly entries of this report.
expense_type_codestringOptionalOnly entries of this expense type code.
payment_type_idstringOptionalOnly entries of this payment type id.
is_billablebooleanOptionalOnly billable (true) or non-billable (false) entries.
userstringOptionalLogin id of the entries' owner (admin role needed).
limitintegerOptionalNumber of entries (default 25).
offsetstringOptionalPage offset from the previous next link.
TypeScript
await setu.execute({
  userId: "user_1",
  action: "sap_concur.list_expense_entries",
  input: {},
});
List expense reports (v3)readsap_concur.list_expense_reportsList expense reports (Expense Reports v3, Concur's most complete filterable list). Without user it returns the signed-in user's own reports; user=ALL needs the Web Services Admin role. Filter by approvalStatusCode (comma list such as A_PEND,A_ACCO; A_APPR approved, A_NOTF not submitted), paymentStatusCode (P_NOTP, P_PAID...), and created/submitted/modified date. Dates are yyyy-MM-dd. Does not include Central Reconciliation reports. Returns reports (with ID usable as report id) and next, the address of the next page.

Needs scope: EXPRPT

InputTypeDescription
userstringOptionalReport owner's login id, or ALL for every owner (admin role needed). Default: the signed-in user.
approval_status_codestringOptionalApproval status code(s), comma-separated, e.g. A_PEND,A_ACCO.
payment_status_codestringOptionalPayment status code(s), comma-separated, e.g. P_NOTP,P_PAID.
create_date_afterstringOptionalReports created after this date (yyyy-MM-dd).
create_date_beforestringOptionalReports created before this date (yyyy-MM-dd).
submit_date_afterstringOptionalReports submitted after this date (yyyy-MM-dd).
modified_date_afterstringOptionalReports modified after this date (yyyy-MM-dd).
limitintegerOptionalNumber of reports to return (default 25).
offsetstringOptionalPage offset from the previous result's next link.
TypeScript
await setu.execute({
  userId: "user_1",
  action: "sap_concur.list_expense_reports",
  input: {},
});
List expenses on a report (v4)readsap_concur.list_report_expensesList the expenses (line items) of one expense report (Expenses v4): type, amounts, transaction date, vendor, payment type, exceptions and receipt flags. Returns an array of expenses; each has an expenseId for get_expense.

Needs scopes: expense.report.read, user.read

InputTypeDescription
user_idstringRequiredThe Concur user's UUID (the 'sub' of the sign-in id_token, or from the Identity API / the user's profile). Ask the person; do not guess.
report_idstringRequiredThe report id.
TypeScript
await setu.execute({
  userId: "user_1",
  action: "sap_concur.list_report_expenses",
  input: {
    "user_id": "<user_id>",
    "report_id": "<report_id>"
  },
});
List travel requestsreadsap_concur.list_travel_requestsList Concur Request travel requests (Travel Request v4). view picks the set: ALL, ACTIVE, UNSUBMITTED, PENDING, SUBMITTED, APPROVED, CLOSED, CANCELED, or TOAPPROVE (requests waiting for the signed-in approver). Pages with start and limit (max 100); the result's operations hold the next-page link. Needs Concur Request.

Needs scope: travelrequest.write

InputTypeDescription
viewstringOptionalWhich requests to list (default ALL).
startintegerOptionalIndex of the first record (default 0).
limitintegerOptionalPage size (default 10, max 100).
modified_afterstringOptionalOnly requests modified after this date (yyyy-MM-dd or UTC date-time).
sort_orderstringOptional
TypeScript
await setu.execute({
  userId: "user_1",
  action: "sap_concur.list_travel_requests",
  input: {},
});
List users (identity v4.1)readsap_concur.list_usersList the company's users from the SCIM Identity v4.1 service (id, active, name, emails via attributes). Concur documents this API for company-level access tokens only, so it may be refused for a normal signed-in user: if it answers 403 or 401, fall back to asking the person for their user id. attributes limits fields, e.g. id,active,emails. Returns users and totalResults.

Needs scope: identity.user.core.read

InputTypeDescription
attributesstringOptionalComma-separated attributes to return, e.g. id,active,emails.
countintegerOptionalPage size (default 100, max 1000).
cursorstringOptionalCursor from the previous page to continue.
TypeScript
await setu.execute({
  userId: "user_1",
  action: "sap_concur.list_users",
  input: {},
});
Send back expense reportwritesap_concur.send_back_expense_reportSend a submitted expense report back to its owner for changes (Workflows v4, PATCH .../context/MANAGER/reports/{reportId}/sendBack). A comment explaining why is required. Returns nothing on success (HTTP 204).

Needs scope: expense.report.workflowstatus.write

InputTypeDescription
user_idstringRequiredThe approver's own Concur user UUID.
report_idstringRequired
commentstringRequiredWhy the report is sent back.
TypeScript
await setu.execute({
  userId: "user_1",
  action: "sap_concur.send_back_expense_report",
  input: {
    "user_id": "<user_id>",
    "report_id": "<report_id>",
    "comment": "<comment>"
  },
});
Submit expense reportwritesap_concur.submit_expense_reportSubmit an unsubmitted expense report into its approval workflow (Workflows v4, PATCH .../reports/{reportId}/submit). The report owner's manager is notified; it can be recalled only if Concur allows it (canRecall). Confirm with the person before submitting. Returns nothing on success (HTTP 204); a 409 means the report cannot be submitted in its current state.

Needs scope: expense.report.readwrite

InputTypeDescription
user_idstringRequiredThe Concur user's UUID (the 'sub' of the sign-in id_token, or from the Identity API / the user's profile). Ask the person; do not guess.
report_idstringRequiredThe report id.
TypeScript
await setu.execute({
  userId: "user_1",
  action: "sap_concur.submit_expense_report",
  input: {
    "user_id": "<user_id>",
    "report_id": "<report_id>"
  },
});