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.
Connect
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
| Scope | Why it is requested |
|---|---|
EXPRPT | Read expense reports and entries and add expenses (Expense v3) |
expense.report.read | Read expense reports and their expenses |
expense.report.readwrite | Create reports, and submit them |
expense.report.workflowstatus.write | Approve or send back expense reports |
user.read | Look up the Concur user id needed for expense report calls |
identity.user.core.read | Read user names and emails from the Identity service |
travelrequest.write | Read 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
| Input | Type | Description | |
|---|---|---|---|
report_id | string | Required | |
expense_type_code | string | Required | Code of an expense type active for the report's policy. |
payment_type_id | string | Required | ID of a payment type active for the report's expense group. |
transaction_date | string | Required | yyyy-MM-dd |
transaction_amount | number | Required | Amount in the transaction currency. |
transaction_currency_code | string | Required | 3-letter ISO 4217 code, e.g. USD. |
description | string | Optional | |
comment | string | Optional | |
user | string | Optional | Login id of the report owner (admin role needed). |
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
| Input | Type | Description | |
|---|---|---|---|
user_id | string | Required | The approver's own Concur user UUID. |
report_id | string | Required | |
comment | string | Optional | Approver comment. |
expense_rejected_comment | string | Optional | Why some expenses were rejected; required if the report has rejected expenses. |
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
| Input | Type | Description | |
|---|---|---|---|
user_id | string | Required | The 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_type | string | Optional | TRAVELER (default) or PROXY. |
name | string | Required | Report name. |
business_purpose | string | Optional | |
comment | string | Optional | |
country_code | string | Optional | ISO 3166-1 country code, e.g. US. |
country_sub_division_code | string | Optional | ISO 3166-2 code, e.g. US-WA. |
policy_id | string | Optional | |
report_date | string | Optional | yyyy-MM-dd |
start_date | string | Optional | yyyy-MM-dd |
end_date | string | Optional | yyyy-MM-dd |
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
| Input | Type | Description | |
|---|---|---|---|
user_id | string | Required | The 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_type | string | Required | The caller's role for the report. |
report_id | string | Required | |
expense_id | string | Required | The expenseId from list_report_expenses (or ExpenseID of a v3 entry). |
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
| Input | Type | Description | |
|---|---|---|---|
entry_id | string | Required | The entry's ID. |
user | string | Optional | Login id of the entry's owner (admin role needed). |
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
| Input | Type | Description | |
|---|---|---|---|
user_id | string | Required | The 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_type | string | Required | The caller's role for the report. |
report_id | string | Required | The report id. |
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
| Input | Type | Description | |
|---|---|---|---|
request_id | string | Required | The request's id (UUID-like, from list_travel_requests). |
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
| Input | Type | Description | |
|---|---|---|---|
user_id | string | Required | The user's UUID. |
attributes | string | Optional | Comma-separated attributes to return. |
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
| Input | Type | Description | |
|---|---|---|---|
report_id | string | Optional | Only entries of this report. |
expense_type_code | string | Optional | Only entries of this expense type code. |
payment_type_id | string | Optional | Only entries of this payment type id. |
is_billable | boolean | Optional | Only billable (true) or non-billable (false) entries. |
user | string | Optional | Login id of the entries' owner (admin role needed). |
limit | integer | Optional | Number of entries (default 25). |
offset | string | Optional | Page offset from the previous next link. |
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
| Input | Type | Description | |
|---|---|---|---|
user | string | Optional | Report owner's login id, or ALL for every owner (admin role needed). Default: the signed-in user. |
approval_status_code | string | Optional | Approval status code(s), comma-separated, e.g. A_PEND,A_ACCO. |
payment_status_code | string | Optional | Payment status code(s), comma-separated, e.g. P_NOTP,P_PAID. |
create_date_after | string | Optional | Reports created after this date (yyyy-MM-dd). |
create_date_before | string | Optional | Reports created before this date (yyyy-MM-dd). |
submit_date_after | string | Optional | Reports submitted after this date (yyyy-MM-dd). |
modified_date_after | string | Optional | Reports modified after this date (yyyy-MM-dd). |
limit | integer | Optional | Number of reports to return (default 25). |
offset | string | Optional | Page offset from the previous result's next link. |
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
| Input | Type | Description | |
|---|---|---|---|
user_id | string | Required | The 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_id | string | Required | The report id. |
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
| Input | Type | Description | |
|---|---|---|---|
view | string | Optional | Which requests to list (default ALL). |
start | integer | Optional | Index of the first record (default 0). |
limit | integer | Optional | Page size (default 10, max 100). |
modified_after | string | Optional | Only requests modified after this date (yyyy-MM-dd or UTC date-time). |
sort_order | string | Optional |
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
| Input | Type | Description | |
|---|---|---|---|
attributes | string | Optional | Comma-separated attributes to return, e.g. id,active,emails. |
count | integer | Optional | Page size (default 100, max 1000). |
cursor | string | Optional | Cursor from the previous page to continue. |
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
| Input | Type | Description | |
|---|---|---|---|
user_id | string | Required | The approver's own Concur user UUID. |
report_id | string | Required | |
comment | string | Required | Why the report is sent back. |
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
| Input | Type | Description | |
|---|---|---|---|
user_id | string | Required | The 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_id | string | Required | The report id. |
await setu.execute({
userId: "user_1",
action: "sap_concur.submit_expense_report",
input: {
"user_id": "<user_id>",
"report_id": "<report_id>"
},
});