BackOffice Calls
Using a BackOffice method is a little different than one from Q2Api or WedgeOnlineBanking. These methods interact with HQ’s BackOffice service, which requires an authenticated BackOffice session. How you obtain that session depends on the type of extension you are building.
Console Extensions
Console extensions run within an active Console CSR User session, so
self.hq_credentials is already a BackOfficeHqCredentials instance.
You can call BackOffice endpoints directly:
from q2_sdk.hq.hq_api.backoffice import GetVersion
params = GetVersion.ParamsObj(self.logger, self.hq_credentials)
response = await GetVersion.execute(params)
Central Extensions
Central extensions run within an active Console or Central CSR User session, so
self.hq_credentials is already a BackOfficeHqCredentials instance.
Usage is identical to Console:
from q2_sdk.hq.hq_api.backoffice import GetVersion
params = GetVersion.ParamsObj(self.logger, self.hq_credentials)
response = await GetVersion.execute(params)
Online Extensions
Online extensions only have a user session token, which does not
grant BackOffice access. You must explicitly establish a BackOffice session
by calling get_backoffice_credentials with a CSR username and password
that has permissions to log into Central:
from q2_sdk.hq.hq_api.backoffice import DecideSuspectGt
back_office_credentials = self.hq_credentials.get_backoffice_credentials(
self.logger,
'user_name',
'user_pwd'
)
params = DecideSuspectGt.ParamsObj(
self.logger,
back_office_credentials,
1,
1,
"This is a test",
False,
)
suspect_results = await DecideSuspectGt.execute(params)
This calls LogonAdmin behind the scenes to mint a BackOffice session
cookie, then uses that session for the endpoint call.
Keep in mind that Central is being sunset, so this approach is not recommended for new development.
Working with Responses
Every BackOffice endpoint returns a BackOfficeResponse instance. Call
.json() to get a usable Python dictionary:
from q2_sdk.hq.hq_api.backoffice import GetVersion
params = GetVersion.ParamsObj(self.logger, self.hq_credentials)
response = await GetVersion.execute(params)
data = response.json()
# {"Version": "2024.1.0.0", ...}
For endpoints that return DataSet-typed responses, .json() automatically parses the embedded XSD schema to produce
properly-typed values. Table rows are always returned as lists of dicts:
from q2_sdk.hq.hq_api.backoffice import GetGroup
params = GetGroup.ParamsObj(self.logger, self.hq_credentials, group_id=1)
response = await GetGroup.execute(params)
data = response.json()
# {
# "TransactionRights": [
# {"TransactionTypeID": 1, "Enabled": True, ...},
# {"TransactionTypeID": 2, "Enabled": False, ...},
# ],
# "Q2_Group": [
# {"GroupID": 1, "GroupDescription": "CSR-L5", ...}
# ],
# "AuditId": 12345,
# }
Integer, boolean, and decimal fields are coerced to their native Python types.
Single-row tables are still returned as a one-element list for consistency.
Scalar fields outside of DataSet tables (like AuditId) are included as
top-level keys with inferred types.
Using DataSet Parameters
Some BackOffice endpoints accept DataSet parameters, which represent typed
.NET DataSet objects serialized over the wire. These endpoints default to the
JSON (.ashx) transport because DataSet serialization over SOAP is not
supported. Each endpoint that uses DataSets has a corresponding _models
module with dataclass models and a builder function. For example,
AddCustomer has AddCustomer_models.
from q2_sdk.hq.hq_api.backoffice import AddCustomer
from q2_sdk.hq.hq_api.backoffice.AddCustomer_models import (
Q2Customer,
Q2Address,
build_dal_customer_edit_dataset,
)
# Build the model instances — use negative IDs for new records
customer = Q2Customer(
customer_id=-1,
customer_name="Jane Doe",
group_id=1,
create_date="2024-01-01",
)
address = Q2Address(
address_id=-1,
city="Austin",
state="TX",
postal_code="78701",
address_type=1,
country_id=1,
)
# Build the DataSet using the generated builder function. Only pass the
# tables you need; each builder argument accepts a single instance or a list.
dal_customer_edit = build_dal_customer_edit_dataset(
q2_customer=customer,
q2_address=address,
)
# Pass the DataSet to the ParamsObj and execute
params = AddCustomer.ParamsObj(
self.logger,
self.hq_credentials,
dal_customer_edit=dal_customer_edit,
)
response = await AddCustomer.execute(params)
AddCustomer also accepts a second, optional customer_rights DataSet for
the customer’s transaction, user, and monetary rights. You build it exactly the
same way — construct the rights model instances, pass them to
build_customer_rights_dataset() (also in AddCustomer_models), and hand the
result to the matching customer_rights parameter:
from q2_sdk.hq.hq_api.backoffice.AddCustomer_models import (
build_customer_rights_dataset,
)
customer_rights = build_customer_rights_dataset(
transaction_rights=...,
user_rights=...,
)
params = AddCustomer.ParamsObj(
self.logger,
self.hq_credentials,
dal_customer_edit=dal_customer_edit,
customer_rights=customer_rights,
)
See the endpoint’s _models module for every available table class and builder.
Updating Existing Records
Update operations that take a DataSet parameter require the current data first
so HQ can perform optimistic concurrency checking: each modified row must be
paired with its unmodified state (the original_record field) so HQ can emit
the “Unchanged” row it compares against. (Without that Unchanged row, HQ rejects
the call.) Update operations that take only plain scalar parameters do not carry
a DataSet, so they have no such requirement.
You do not need to wire this up by hand. Every DataSet-based Update endpoint’s
generated module exposes a fetch_current() helper (alongside execute())
that calls the matching GET operation, parses the response into typed model
instances, and pre-populates original_record on each one. Modify the
returned instances, build the DataSet, and execute:
from q2_sdk.hq.hq_api.backoffice import UpdateUserLogon
from q2_sdk.hq.hq_api.backoffice.UpdateUserLogon_models import (
build_dal_user_logon_dataset,
)
# 1. Fetch current state (original_record is pre-populated on each instance)
current = await UpdateUserLogon.fetch_current(
self.logger, self.hq_credentials, user_logon_id=12345
)
# 2. Modify the returned dataclass instance(s)
user_logon = current["Q2UserLogon"][0]
user_logon.login_name = "new_login_name"
# 3. Build the DataSet and execute. Here the ``dal_user_logon`` parameter is
# the DalUser_Logon DataSet.
dal_user_logon = build_dal_user_logon_dataset(q2_user_logon=user_logon)
params = UpdateUserLogon.ParamsObj(
self.logger, self.hq_credentials, dal_user_logon=dal_user_logon
)
response = await UpdateUserLogon.execute(params)
Note
Some Update endpoints take more than one DataSet parameter. For example,
UpdateUserProfile accepts both dup (the user profile) and an optional
user_rights DataSet, and UpdateGroup accepts group_edit plus
group_rights. The pattern is the same — call fetch_current(), modify
the relevant instances, then build and pass each DataSet — but the model
classes and builder functions differ per endpoint. Check the endpoint’s
_models module (e.g.
UpdateUserProfile_models) for the exact
model classes and the corresponding build_*_dataset() functions it
provides (one per DataSet parameter).