The most complete Python client for the SAP Concur API — covering Expense Reports v4, Travel Itineraries, Users, Receipts, Attendees, and more. OAuth2 out of the box, fully typed, async-ready.
Built with ❤️ by TEVASOFT — creators of EVA, the AI-powered expense audit & e-invoicing platform.
The SAP Concur API is powerful but underdocumented for Python developers. This client provides:
- Full OAuth2 lifecycle — password grant, company JWT, client credentials, token refresh
- All major APIs covered — Expense Reports, Entries, Receipts, Travel, Users, Attendees, Allocations
- Async support —
AsyncConcurClientfor high-throughput pipelines - Pydantic models — all API responses are typed and validated
- Pagination handled — iterators that transparently page through thousands of records
- Multi-datacenter — EU, US, CN, AU endpoints
- CLI included —
concur reports list,concur users get, ...
pip install sap-concur-pythonfrom concur import ConcurClient
client = ConcurClient(
client_id="YOUR_CLIENT_ID",
client_secret="YOUR_CLIENT_SECRET",
company_token="YOUR_COMPANY_TOKEN", # server-to-server
datacenter="eu",
)
# Fetch approved expense reports
for report in client.expense.reports.list(approval_status="A_APPR"):
print(f"{report.id} | {report.name} | {report.total} {report.currency_code}")
# Get a single report with all its entries
report = client.expense.reports.get("REPORT_ID")
for entry in report.entries:
print(f" {entry.expense_type_name}: {entry.approved_amount} {entry.report_currency}")
# Download receipt images
image_bytes = client.expense.receipts.get_image("IMAGE_ID")
# List company users
for user in client.users.list(active=True):
print(f"{user.login_id} — {user.display_name}")| Resource | API Version | Methods |
|---|---|---|
| Expense Reports | v4 | list, get, create, submit, recall |
| Expense Entries | v4 | list, get, create, update, delete |
| Expense Attendees | v4 | list, get |
| Expense Allocations | v4 | list |
| Receipt Images | v4 | get, upload, list |
| Travel Itineraries | v4 | list, get |
| Travel Trips | v4 | list |
| Users (Identity) | v4 | list, get, create, update |
| Company Info | v1.1 | get |
| Lists & List Items | v4 | list, get, create |
| Financial Integration | v4 | get_documents |
client = ConcurClient(
client_id="...",
client_secret="...",
company_token="...", # From your Concur admin
datacenter="eu",
)client = ConcurClient(client_id="...", client_secret="...", datacenter="eu")
client.auth.authenticate(username="user@corp.com", password="...")client = ConcurClient(client_id="...", client_secret="...", datacenter="eu")
client.auth.authenticate_client_credentials()from concur import AsyncConcurClient
async with AsyncConcurClient(client_id="...", client_secret="...",
company_token="...", datacenter="eu") as client:
reports = await client.expense.reports.list_async(approval_status="A_APPR")
tasks = [client.expense.reports.get_async(r.id) for r in reports[:10]]
full_reports = await asyncio.gather(*tasks)All list() methods return iterators — no manual offset handling needed:
# Iterates through ALL approved reports automatically
for report in client.expense.reports.list(
approval_status="A_APPR",
submit_date_after="2026-01-01",
):
process(report)
# Or fetch as a list (loads all into memory)
reports = client.expense.reports.list_all(
approval_status="A_APPR",
limit=1000,
)# Reports
report = client.expense.reports.get("RPRT1234")
entries = client.expense.entries.list(report_id="RPRT1234")
allocations = client.expense.allocations.list(report_id="RPRT1234")
attendees = client.expense.attendees.list(entry_id="ENTRY_ID")
# Receipts
image_bytes = client.expense.receipts.get_image("IMG_ID")
images_meta = client.expense.receipts.list_images(report_id="RPRT1234")
# Submit a report
client.expense.reports.submit("RPRT1234")
# Recall (unsubmit)
client.expense.reports.recall("RPRT1234")# Itineraries
for trip in client.travel.itineraries.list(start_date="2026-01-01"):
print(f"{trip.trip_name}: {trip.start_date} → {trip.end_date}")
for segment in trip.segments:
print(f" {segment.type}: {segment.origin} → {segment.destination}")
# Trips
trips = client.travel.trips.list(active=True)# List active users
for user in client.users.list(active=True):
print(f"{user.login_id}: {user.display_name} ({user.country_code})")
# Get specific user
user = client.users.get_by_login("jean.dupont@acme.fr")
# Update user
client.users.update(user.uuid, {"active": False})# Get financial documents for approved reports (for ERP posting)
docs = client.financial.get_documents(batch_id="BATCH_001")
for doc in docs:
for line in doc.journal_entries:
print(f" {line.account_code}: {line.debit or line.credit} {line.currency}")# Reports
concur reports list --status approved --from 2026-01-01
concur reports get REPORT_ID
concur reports export --format csv --output reports.csv
# Entries
concur entries list --report REPORT_ID
# Users
concur users list --active
concur users get jean.dupont@acme.fr
# Auth
concur auth test # verify credentials workTEVASOFT builds EVA, an AI-powered platform for expense report audit and e-invoicing compliance for enterprise.
Apache 2.0