Last updated

API Introduction

Welcome to the BillingPlatform API documentation, your gateway to seamless integration and interaction with our billing and subscription management system. Our API empowers developers to effortlessly incorporate BillingPlatform's robust functionalities into their applications, enabling efficient data retrieval, manipulation, and management.

Interaction Methods

Query API

The Query API serves as the primary and preferred method for retrieving data from the BillingPlatform system. Users can submit queries to the API, which are executed, and a corresponding result set is returned. This method ensures efficient data retrieval while maintaining system integrity and security.

CRUD API

The CRUD API exposes methods for creating, deleting, and updating data within the BillingPlatform system based on an entity-level CRUD pattern (Create, Read, Update, Delete). When using the CRUD API to update existing records, the Upsert method is recommended as the preferred way to ensure data accuracy and consistency across the platform.

Bulk Extract API

The Bulk Extract API is designed to efficiently retrieve large data sets in batches. This method is ideal for scenarios where substantial amounts of data need to be extracted quickly to be processed off platform.

Procedures

Integrating with the BillingPlatform API can be done through SOAP or REST. Click on one of the links below to jump to the desired approach.

Getting Started

The BillingPlatform API is designed to operate as an extension of your standard and custom data model with rich, database-style methods for every object utilizing Create, Retrieve, Query, Update, Delete and Upsert operations.

The Upsert operation allows you to specify an external key on which to either Create or Update a record based on the existence of the supplied key.

Each Web Service interaction begins with the acquisition of a session ID obtained via the log-in method. Once the session ID is retrieved, it can be reused in subsequent operations.

These simple operations coupled with access to all of the objects in your organization's data model will serve as powerful building blocks for a comprehensive Web Service integration. The general methods available in the Web Service API are described below.

General Concepts

A valid API user is needed to authenticate and retrieve a session ID/token from the platform.

  • Reach out to your platform support contact if assistance is needed to create an API user. If administrator rights have been provided previously, a new API user can be created with the appropriate access. Refer to the Creating an Application User article for more information.

When authoring API requests to push or pull data into or from the platform, the following concepts can be considered.

The following example shows how to create a BILLING_PROFILE:

{
    "brmObjects": [
        {
            "AccountId": "70979",
            "Attention": "Mr. T",
            "BillTo": "The A-Team",
            "Address1": "1200 Main St",
            "TimeZoneId":"350",
            "InvoiceTemplateId":"122",
            "BillingCycle": "MONTHLY",
            "MonthlyBillingDate": "1",
            "PaymentTermDays": "0",
            "BillingMethod": "MAIL",
            "CurrencyCode": "USD"
        }
    ]
}

Lookups can be used with IDs, but you can also lookup object IDs by an External Key.

  • For many of the standard BillingPlatform objects, the Name field serves as its natural external key.

  • For custom objects or standard objects, custom fields can be designated as external keys during setup.

    {
        "brmObjects": [
            {
                "AccountId": "70977",
                "Attention": "Mr. T",
                "BillTo": "The A-Team",
                "Address1": "1200 Main St",
                "TimeZoneIdObj": {
                    "Tzname": "US/Mountain"
                },
                "InvoiceTemplateIdObj": {
                    "Name": "Default Invoice Template"
                },
                "BillingCycle": "MONTHLY",
                "MonthlyBillingDate": "1",
                "PaymentTermDays": "0",
                "BillingMethod": "MAIL",
                "CurrencyCode": "USD"
            }
        ]
    }
    

Note that in the above examples the attribute

"TimeZoneId":"350",

becomes

"TimeZoneIdObj": {
                "Tzname": "US/Mountain"
            },

showing that it maps the ID of the timezone whose object name matches.

If you query timezones, such as:

SELECT Id,
    Tzname,
    UtcOffset
FROM TIME_ZONE
WHERE TzName = 'US/Mountain'

you will get the following:

{
"queryResponse": [
    {
    "Id": "350",
    "Tzname": "US/Mountain",
    "UtcOffset": "-07:00"
    }
]
}

Your First API Request

Once you have your API user created, you'll need to authenticate by requesting an access token.

Note: Once you've completed these steps, you'll be able to leverage the `/login` endpoint to authenticate and receive a sessionId.

Requesting an Access Token

There are three different methods available for retrieving a token:

  • Username and Password: Retrieve a token using the user's credentials sent via the URL.

  • Client Secret: Obtain a token by providing the Client ID and secret in the URL.

  • Encrypted Client Secret: Retrieve a token by sending the encrypted client secret in the Authorization header. This method is more secure and recommended.

Each method uses the same organization-specific URL:

HTTP Method POST:

https://[your BillingPlatform environment].billingplatform.com/<orgName>/auth/2.0/authenticate

Username and Password

This method for retrieving a token uses OAuth authentication with a username and password. The grant_type parameter must be set to password to indicate that the authentication method is based on the username and password:

  • grant_type - Set to password.

  • username - This is your unique identifier for BillingPlatform.

  • password - This is your password for BillingPlatform.

Here is an example of a request to the authorization server:

HTTP Method: POST

https://[your BillingPlatform environment]/auth/2.0/authenticate?grant_type=password&username=YOUR_USERNAME&password=YOUR_PASSWORD

In this example, you would need to replace [your BillingPlatform environment] with the appropriate URL for your authorization server, and replace YOUR_USERNAME and YOUR_PASSWORD with the credentials for BillingPlatform.

Authentication

Authentication is a critical component of interacting with the BillingPlatform API. This chapter will guide you through the two authentication mechanisms (Client ID/Secret an Certificate) supported by the API, ensuring secure and authorized access to your data.

Authentication Overview

BillingPlatform supports two methods of authentication to ensure secure access to your data: Client ID/Secret and Certificate

Obtain a Session ID with a POST Call

Obtaining a session ID is a straightforward way to secure your API requests. This method involves making a POST request to the authentication endpoint with your credentials to receive a session ID, which you include in subsequent requests.

Steps to Obtain a Session ID

  1. Get a BillingPlatform Account with API Access: How to validate that your BP user account has API access. Add screen shots allow API.
  2. Generate Session ID with Login call: Ensure you have your username and password ready.

Making the POST Call

To obtain a session ID, make a POST request to the authentication endpoint with your username and password in the body of the request.

Login Call URL:

https://my.billingplatform.com/myorg/rest/2.0/login

Example Request (body) Payload:

{
    "username": "my.username",
    "password": "password"
}

Postman Example Login POST Request

Postman Login POST Response

  • A successful request should return a sessionId

  • This sessionId is required for every other API request, see Using the Session ID

Handling the Response

If the credentials are valid, the response will include a session ID that you can use for subsequent API requests.

Example Response:

{
    "loginResponse": [
        {
            "SessionID": "thisWillBeAStringOfCharacters",
            "ErrorCode": "0",
            "ErrorText": []
        }
    ]
}

Using the Session ID

Include the session ID in the Authorization header of each API request. The session ID should be prefixed with sessionid .

Example API Request with Session ID (cURL):

curl --location 'https://{env}.billingplatform.com/{org}/rest/2.0/ACCOUNT/12345' \
--header 'sessionid: thisWillBeAStringOfCharacters' \
--data ''

Example API Request with Session ID (JavaScript, Fetch):

const myHeaders = new Headers();
myHeaders.append("sessionid", "thisWillBeAStringOfCharacters");

const raw = "";

const requestOptions = {
    method: "GET",
    headers: myHeaders,
    body: raw,
    redirect: "follow"
};

fetch("https://{env}.billingplatform.com/{org}/rest/2.0/ACCOUNT/12345", requestOptions)
    .then((response) => response.text())
    .then((result) => console.log(result))
    .catch((error) => console.error(error));

Securing Your Session ID

To ensure the security of your session ID, follow these best practices:

  • Keep It Secret: Never share your session ID publicly or embed it directly in client-side code.
  • Monitor Usage: Regularly monitor API usage to detect any unusual or unauthorized activity.

Error Handling

If your session ID is missing or invalid, the API will return an authentication error. Handle these errors gracefully in your application to provide a better user experience.

Example Error Response:

{
"error": "unauthorized",
"message": "Invalid session ID"
}

Logging Out

When you are finished with your session, you should log out to invalidate the session ID. This is done via a POST request to the logout endpoint.

Logout Call URL:

https://my.billingplatform.com/myorg/rest/2.0/logout

Example Request:

POST /rest/2.0/logout HTTP/1.1
Host: api.billingplatform.com
Authorization: Session YOUR_SESSION_ID

OAuth 2.0 Authentication

BillingPlatform can use OAuth 2.0 to authenticate requests as legitimate and authorized. With OAuth 2.0, you can access instance resources with a token rather than by entering login credentials with each resource request.

Enabling OAuth Globally

To ensure that all relevant components, endpoints, and settings within the system are configured to support OAuth authentication, enable OAuth globally by follow these steps:

  1. Navigate to Setup > Security > OAuth Settings.
  2. Enable the Allow API access via OAuth option. For a detailed procedure, refer to OAuth Settings

Retreiving a Client ID

Once you have completed these steps, you will be able to use the username/password login method. This login method is similar to a standard login and uses the same endpoints and headers as the rest of the implementation. However, to fully use OAuth, you need to set up OAuth keys, which are described in Inbound OAuth Keys

To configure OAuth keys, set the Authentication Method to Select Client ID/Secret and fill in the remaining fields. Note that the API User field indicates that the token will have the same access permissions as the specified user.

When configuring OAuth keys, a Client ID is generated. This Client ID serves as a unique identifier for the specific secret/certificate that you will use for authorization purposes within the platform. By associating the Client ID with a corresponding secret or certificate, the platform can accurately identify and authenticate the client requesting access.

The Client ID acts as a reference point, allowing the platform to match the incoming authorization requests with the correct secret or certificate associated with that particular client. This ensures that the authorization process is secure and that access is granted only to the authorized client.

An alternative Authentication Method option is a Certificate instead of Client ID / Secret. In addition to the usual fields found in the Client Secret configuration, there is one extra field called Certificate (X.509) that needs to be filled in. This certificate is an rsa:4096 sha256 X.509 certificate generated together with a private key.

To provide the required certificate, click Browse button inside the field and locate the specific certificate file on your system. This certificate file will be used in the OAuth configuration process.

OAuth Settings

In BillingPlatform, there are multiple ways to authenticate your API requests. You can either use a SessionID or utilize OAuth. In some cases, you may want to be able to authenticate only via one of these options. Or you may want to disable API authentication completely. You can also configure the time-to-live of access tokens issued by BillingPlatform.

Procedure

  1. To update your OAuth Settings, go to Setup > Security > OAuth Settings.
  2. Click Edit
  3. To Allow API access via OAuth, select the first checkbox. This setting controls whether API requests can authenticate via OAuth. Any inbound/outbound OAuth keys you have created will only work if this option is enabled. Only disable this setting if you plan to authenticate with user sessions instead, or if you wish to completely disable API authentication.
  4. To Allow API access via user session, select the second checkbox. This setting controls whether API requests can authenticate with a SessionID (generated by sending a special API request with your username and password). Only disable this setting if you plan to use OAuth instead, or if you wish to completely disable API authentication.
  5. In the Access Token TTL (minutes), specify the the lifespan of access tokens issued by BillingPlatform for inbound OAuth requests. When an access token expires, the external OAuth client will automatically reauthenticate or use a refresh token (depending on your configuration) to get a new access token. For information about configuring inbound OAuth, see Inbound OAuth Keys

Query API

The Query API is the cornerstone of data retrieval within the BillingPlatform system. It allows users to execute custom queries to fetch the data they need in a precise and efficient manner. This chapter will guide you through the fundamentals of using the Query API, including how to construct queries, interpret results, and leverage advanced features to optimize your data retrieval processes.

Key Features

  • Flexible Querying: Construct complex queries using a variety of parameters to retrieve exactly the data you need.
  • Efficient Data Retrieval: Designed for speed and efficiency, the Query API ensures minimal latency and optimal performance.
  • Secure Access: All data interactions are secured with industry-standard authentication and authorization mechanisms.

Getting Started with the Query API

Constructing a Query

Queries are constructed using a standard query language and provided in the "sql" parameter of the request. Below are some common query components:

  • SELECT: Specifies the fields to be retrieved.
  • FROM: Indicates the data source or table.
  • WHERE: Filters the results based on specific conditions.
  • ORDER BY: Sorts the results.
  • AS: Renames a column or expression in the result set.

Note: The alias assigned with the AS portion of the query cannot be the same as any other column name in the query.

Example: Basic Query

SELECT customer_id AS id, name, email
FROM customers
WHERE status = 'active'
ORDER BY name ASC

Sending a Query Request

To send a query, make an HTTP GET request to the Query API endpoint with your query as a parameter.

Example:

GET https://sandbox.billingplatform.com/<yourOrg>/rest/2.0/query?sql=SELECT%20customer_id%20AS%20id,%20name,%20email%20FROM%20customers%20WHERE%20status%20=%20'active'%20ORDER%20BY%20name%20ASC
Host: sandbox.billingplatform.com
Authorization: Bearer YOUR_API_KEY

Handling the Response

The Query API returns results in a structured JSON format. Below is an example of a typical response:

Response Example:

{
"queryResponse": [
    {
    "Id": null,
    "Status": "CLOSED",
    "TOTAL": "25024"
    },
    {
    "Id": null,
    "Status": "CURRENT",
    "TOTAL": "101"
    }
]
}

Advanced Query Features

Pagination

For large datasets, use pagination to manage and navigate through results efficiently. You can specify the page number and page size in your query parameters.

Example:

GET https://sandbox.billingplatform.com/<yourOrg>/rest/2.0/query?sql=SELECT%20customer_id%20AS%20id,%20name,%20email%20FROM%20customers%20WHERE%20status%20=%20'active'%20ORDER%20BY%20name%20ASC&page=1&page_size=50
Host: sandbox.billingplatform.com
Authorization: Bearer YOUR_API_KEY

Filtering and Sorting

Enhance your queries with advanced filtering and sorting capabilities to retrieve the most relevant data.

Example:

SELECT invoice_id, amount, due_date
FROM invoices
WHERE due_date > '2024-01-01'
ORDER BY due_date DESC

Best Practices

  • Optimize Queries: Always aim to write efficient queries to reduce load times and improve performance.
  • Handle Errors Gracefully: Implement robust error handling to manage API response errors effectively.

BillingPlatform Query Language

The BP query language offers a powerful tool for retrieving data over both the SOAP and REST APIs as well as the Web toolkit. All standard and custom objects and their relationships are available in BP SQL.

The BP Query language is structured much like other SQL languages with a number of exceptions:

  1. Explicit Table or Entity joins are not supported
  2. Relationships are referenced via the structured hierarchies constructed in the BP data model
  3. Only one Entity can be referenced in the From clause
  4. Use of the asterisk is not supported (ex: select *)
  5. Query results are influenced by the Sharing Group configuration of the API user performing the query. If the API user is not supposed to access certain records based on the Sharing Group filter for its profile, the records will not be returned in the query response.
  6. SELECT queries preceding a UNION operator must have a WHERE clause. If there's no condition that needs to be specified, you can use WHERE 1=1.

The following is a simple example of selecting data from the INVOICE entity:

SELECT GrandTotalAmount, PaymentAmount, PaymentStatus, ClosedDate
FROM Invoice

The structure of the syntax is as follows:

SELECT fieldList [,subquery][,...]
FROM entity
[WHERE conditionExpression 
[AND conditionExpression]
[OR conditionExpression]
[ROWNUM CONDITION]]
[GROUP BY {fieldGroupByList}
            [HAVING havingConditionExpression] ]
[ORDER BY fieldOrderByList {ASC|DESC}]

Below are some examples of what your queries could include.

The Syntax Item column has items that were referred to in the code sample above.

Syntax ItemDescription
fieldListList of fields from an entity in BillingPlatform. To check which fields are available for a certain entity, go to Setup > Develop > Entities, select an entity, and then click Fields. Alternatively, you can view the Reference section of the API documentation.
A list of fields for the Account entity could be something like this: Name, Status, AccountTypeID. Here's how that looks in a query:

SELECT Name, Status, AccountTypeID FROM AccountWHERE Id = '70860'
subQueryAllows including records from a child relationship. Also referred to as an "inline view" in other languages:

SELECT GrandTotalAmount, (SELECT Cost, TaxCost from Activity.InvoiceIdObj) FROM Invoice

Subqueries can be further filtered using WHERE as needed:

SELECT GrandTotalAmount, (Select Cost, TaxCost from Activity.InvoiceIdObj WHERE Cost > 0.00) FROM Invoice
entityThe focus or root of the query. Only one Entity can be specified in the WHERE clause. The entity is not case-sensitive.
conditionExpressionConditions on what will be filtered in the SQL statement. Can include sub-queries, such as:

SELECT Id, Name FROM PACKAGE WHERE Id IN (select PackageProductIdObj.PackageIdObj.Id FROM CONTRACT_RATE) AND rownum < 5
fieldGroupByListList of fields to Group By when using Aggregate functions, such as SUM and COUNT:

SELECT SUM(GrandTotalAmount) total, Status FROM Invoice GROUP BY Status
havingConditionExpressionAllows you to further filter a Grouped Result:

SELECT SUM(GrandTotalAmount) Total, Status FROM Invoice GROUP BY Status HAVING sum(GrandTotalAmount) > 1000
fieldOrderByListControls how the query results are ordered, by Ascending or Descending order.

SELECT GrandTotalAmount, PaymentAmount, PaymentStatus, ClosedDate FROM Invoice ORDER BY GrandTotalAmount DESC, ClosedDate ASC

Relationship Queries

Relationships are defined in BillingPlatform using either the Lookup field or the Embedded List control. Relationships are referenced using the Obj syntax:

[Parent Lookup Field]Obj

To reference a parent object’s field, use the following syntax:

[Parent Lookup Field]Obj.[Parent Field]

For example, a lookup on the Invoice object to the BillingProfile entity called BillingProfile will be called BillingProfileObj. If you were to reference the parent BillingProfile’s Address1 field it would look like this:

SELECT BillingProfileObj.Address1 FROM Invoice

Multi-level parents (e.g., Invoice > Billing Profile > Account) can be accessed by using the same reference model continuously. For example:

SELECT BillingProfileObj.AccountObj.Name from INVOICE

Child relationships are accessed using sub-queries. The SELECT and WHERE clauses of the sub-query are identical to the standard query language. The FROM clause uses the following construct:

[Child Entity].[Parent Lookup Field]Obj

For example, the relationship between the Invoice entity and the BillingProfile entity, using the BillingProfile as the driver can be referenced in a sub-query as follows:

SELECT Id, AccountObj.Name  , (SELECT GrandTotalAmount  FROM Invoice.BillingProfileObj) FROM BillingProfile 

In the above example, the Invoice entity has a lookup filed referencing the BillingProfile entity called BillingProfile.

Alternatively, the [Child Entity].[Parent Lookup Field]Obj can be represented as [Child Entity]__[ParentLookup Field]Obj, where the "." is replaced by double underscores "__". This will make the query consistent with the other object references within the app, especially if you are familiar with the platform's Web toolkit.

Avoiding Sub-queries For Child Objects

You can also directly access fields of child objects relative to the entity used on your `FROM`` clause. This can be done by using the following notation:

[Child Entity]__[Parent Lookup Field]Obj.[Child Field]

For example, to access the Id and GrandTotalAmount fields on the INVOICE when creating a query using BILLING_PROFILE on the FROM clause, you can use either of the following methods. Note that this can only be done if the double underscore notation is used on the query:

Sub-Query:

SELECT Id, (    SELECT Id, GrandTotalAmount     FROM Invoice__BillingProfileIdObj   ) FROM Billing_Profile WHERE Id = 12345

Child Field Access:

SELECT Id, Invoice__BillingProfileIdObj.Id,     Invoice__BillingProfileIdObj.GrandtotalAmount FROM Billing_Profile WHERE Id = 12345

The advantage of using Child Field Access is that you can apply aggregate functions when you cannot perform them using sub-queries.

You can also go down multiple levels in the data model. For example, an Invoice record is associated to a Billing Profile record, which in turn is mapped to an Account record. To pull fields from the Invoice object while using the Account object as the source/anchor in the SQL c/o the FROM clause, the following can be done.

Sub-Query

SELECT id,
        NAME,
        (SELECT id,
                (SELECT id,
                        grandtotalamount
                FROM   invoice.billingprofileidobj)
        FROM   billing_profile.accountidobj)
FROM   account
WHERE  id = 34610 

Child Field Access

SELECT id, name, Billing_Profile__AccountIdObj.Id, 
Billing_Profile__AccountIdObj.Invoice__BillingProfileIdObj.Id, 
Billing_Profile__AccountIdObj.Invoice__BillingProfileIdObj.GrandTotalAmount 
FROM account WHERE id = 34610

Here are some examples of how queries with relationships would be executed using the SOAP API:

  • Sample SOAP Request showing two levels of relationships in a nested sub-query
<SOAPENV
:envelopeXMLNS
:soapenv="http://schemas.xmlsoap.org/soap/envelope/"XMLNS
:urn="urn:stronglytyped.soap.brmsystems.com"> 
<SOAPENV:header> 
<URN:sessionheader> 
<URN:sessionid>hmemtcxkwaveanvkpupooszawlyepdylbvhlqbsz 
</URN:sessionid> 
</URN:sessionheader> 
</SOAPENV:header> 
<SOAPENV:body> 
<URN:query> 
<URN:sql>SELECT accountobj.NAME ← parent relationship,
    accountobj.type,
    accountobj.status,
    address1,
    address2,
    city,
    state,
    zip,
    (
            SELECT grandtotalamount ← child relationship/sub-query,
                    paymentamount,
                    paymentstatus,
                    closeddate
            FROM   invoice.billingprofileobj),
    (
            SELECT amount ← nested child relationship/sub-query,
                    paymentdate,
                    paymenttype,
                    billingprofileobj.accountobj.NAME
            FROM   payment.billingprofileobj)
FROM   billing_profile ← driver entity
WHERE  accountobj.NAME LIKE 'Blue Moon%'WHERE  referencing parent field </URN:sql> </URN:query> </SOAPENV:body> </SOAPENV:envelope>
  • Sample SOAP Response Showing Hierarchical Result Set with Nested Objects.
<soapenv:Envelope
    xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:urn="urn:stronglytyped.soap.brmsystems.com" >
    <soapenv:Body>
        <urn:QueryResponse
            xmlns:urn="urn:stronglytyped.soap.brmsystems.com"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <urn:Result xsi:type="urn:BILLING_PROFILE">
                <urn:Id>40973</urn:Id>
                <urn:Address1>RTP Address1</urn:Address1>
                <urn:Address2></urn:Address2>
                <urn:City>RTP City</urn:City>
                <urn:State>Colorado</urn:State>
                <urn:Zip>80111</urn:Zip>
                <urn:PaymentBillingProfileObj>
                    <urn:Id>449553</urn:Id>
                    <urn:Amount>0</urn:Amount>
                    <urn:PaymentDate>2015-05-15T09:33:06.241000-06:00</urn:PaymentDate>
                    <urn:PaymentType>CREDIT CARD</urn:PaymentType>
                    <urn:BillingProfileObj>
                        <urn:Id>40973</urn:Id>
                        <urn:AccountObj>
                            <urn:Id>10205</urn:Id>
                            <urn:Name>Blue Moon 
-- ATT</urn:Name>
                        </urn:AccountObj>
                    </urn:BillingProfileObj>
                </urn:PaymentBillingProfileObj>
                <urn:InvoiceBillingProfileObj>
                    <urn:Id>414106</urn:Id>
                    <urn:GrandTotalAmount>101</urn:GrandTotalAmount>
                    <urn:PaymentAmount>0</urn:PaymentAmount>
                    <urn:PaymentStatus>NOT PAID</urn:PaymentStatus>
                    <urn:ClosedDate></urn:ClosedDate>
                </urn:InvoiceBillingProfileObj>
                <urn:AccountObj>
                    <urn:Id>10205</urn:Id>
                    <urn:Name>Blue Moon 
-- ATT</urn:Name>
                    <urn:Type>Partner Retail</urn:Type>
                    <urn:Status>ACTIVE</urn:Status>
                </urn:AccountObj>
            </urn:Result>
        </urn:QueryResponse>
    </soapenv:Body>
</soapenv:Envelope>  

Fields from Related Entities can be selected for up to 6 levels deep for parent Entity attributes and up to three levels deep for child, sub-queries. The example below demonstrates a multi-level, parent Query executed over the SOAP API:

SOAP request:

<soapenv:Envelope
    xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:urn="urn:stronglytyped.soap.brmsystems.com">
    <soapenv:Header>
    <urn:SessionHeader>
        <urn:SessionID>hmeMTcxKWavEAnvkpupOOszawLyePdyLbVhlQBsz</urn:SessionID>
    </urn:SessionHeader>
    </soapenv:Header>
    <soapenv:Body>
        <urn:Query>
            <urn:Sql>SELECT 
        InvoiceId,
        Amount,
        InvoiceObj.GrandTotalAmount ← one level up,
        InvoiceObj.BillingProfileObj.City ← two levels up,
        InvoiceObj.BillingProfileObj.AccountObj.Name ← three levels up,
        InvoiceObj.BillingProfileObj.State,
        InvoiceObj.BillingProfileObj.Zip,
        PaymentItemIdObj.Amount
        FROM PAYMENT_ALLOCATION
    </urn:Sql>
        </urn:Query>
    </soapenv:Body>
</soapenv:Envelope> 

SOAP response:

<soapenv:Envelope
    xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:urn="urn:stronglytyped.soap.brmsystems.com">
    <soapenv:Body>
        <urn:QueryResponse>
            <urn:Result xsi:type="urn:PAYMENT_ALLOCATION">
                <urn:Id>448513</urn:Id>
                <urn:InvoiceId>414260</urn:InvoiceId>
                <urn:Amount>750</urn:Amount>
                <urn:InvoiceObj>
                    <urn:Id>414260</urn:Id>
                    <urn:GrandTotalAmount>1114</urn:GrandTotalAmount>
                    <urn:BillingProfileObj>
                        <urn:Id>37730</urn:Id>
                        <urn:City>Fort Collins</urn:City>
                        <urn:State>CO</urn:State>
                        <urn:Zip>80567</urn:Zip>
                        <urn:AccountObj>
                            <urn:Id>13087</urn:Id>
                            <urn:Name>Running With Scissors</urn:Name>
                        </urn:AccountObj>
                    </urn:BillingProfileObj>
                </urn:InvoiceObj>
                <urn:PaymentItemIdObj>
                    <urn:Id>448512</urn:Id>
                    <urn:Amount>750</urn:Amount>
                </urn:PaymentItemIdObj>
            </urn:Result>
            <urn:Result xsi:type="urn:PAYMENT_ALLOCATION">
                <urn:Id>448499</urn:Id>
                <urn:InvoiceId>448411</urn:InvoiceId>
                <urn:Amount>1000</urn:Amount>
                <urn:InvoiceObj>
                    <urn:Id>448411</urn:Id>
                    <urn:GrandTotalAmount>2363.87</urn:GrandTotalAmount>
                    <urn:BillingProfileObj>
                        <urn:Id>55123</urn:Id>
                        <urn:City>Budapest</urn:City>
                        <urn:State/>
                        <urn:Zip>61685</urn:Zip>
                        <urn:AccountObj>
                            <urn:Id>26335</urn:Id>
                            <urn:Name>Accelsiors LTD</urn:Name>
                        </urn:AccountObj>
                    </urn:BillingProfileObj>
                </urn:InvoiceObj>
                <urn:PaymentItemObj>
                    <urn:Id>448498</urn:Id>
                    <urn:Amount>1000</urn:Amount>
                </urn:PaymentItemIdObj>
            </urn:Result>
        </urn:QueryResponse>
    </soapenv:Body>
</soapenv:Envelope>

Updating LOOKUP type fields can be accomplished using two methods:

  • Passing the record ID of the lookup entity's record.

  • Passing the external key field value of the lookup entity's record.

Bulk Extract API

Bulk Extract API is used to efficiently process large data requests and retrieve massive amounts of data in batches, each containing a user-specified number of records. Bulk Extract requests can be based on one the following:

  • Queries created with the BillingPlatform Query Language

  • The RETRIEVE method that is better suitable for retrieving a large amount of data on a specific entity in your enterprise billing system.

The retrieved data can be formatted as JSON or as CSV with the user-defined delimiters, qualifiers, and end-line format characters. You can select either response format for query requests or for requests based on the RETRIEVE method.

To extract large datasets from the Mediation Data Lake (MDL), use the MDL EXTRACT method. This method extracts data asynchronously, which allows for handling complex data retrieval scenarios, providing customizable data formatting options, and ensuring efficient tracking and management of extraction processes. This method supports only CSV format output.

The Bulk Request Processing Process must be active in order to process a Bulk Extract API request. Otherwise the request remains in a PENDING status and is not processed. The Bulk Request Processing process can be activated (or inactivated) on the Process Console page by a user with sufficient privileges (see Using the Process Console for more information).

Entities Activity and Collector_Fields can be set to save deleted entity log records. These records can later be extracted by the Bulk Extract API. Please contact your BillingPlatform representative to activate this functionality.

Bulk Extract query request


To send a Bulk Extract request using the Query method, issue a POST /rest/2.0/bulk_api_request call with the following parameters:

FieldData TypeDescription
RequestNameTEXTDescriptive name of the new request.
RequestBodyTEXTAREAThe request payload created using the BillingPlatform Query Language. The payload length cannot exceed 4000 characters.
RecordsPerBatchNUMBERNumber of records returned in one batch. For example, prior to running a request that is bound to return about a million records, consider splitting this amount into smaller batches of 50 thousand records each for easier processing.
RequestMethodTEXT- QUERY
- RETRIEVE
- MDL EXTRACT
ResponseFormatTEXTJSON

Sample Bulk Extract query request

URL: https://my.billingplatform.com/myorg/rest/2.0/bulk\_api\_request

Method: POST

{   "brmObjects":{      
        "RequestName":"Request Name",      
        "RequestBody":"select AccountId as AccountId,                
        AccountPackageId as AccountPackageId,                
        AccountProductId as AccountProductId,               
        ActivityCollectorId as ActivityCollectorId,                
        ActivityDate as ActivityDate,                
        BillingIdentifierId as BillingIdentifierId,         
        ...         
        RateSource as RateSource  from activity where rownum <= 100000",
        "RecordsPerBatch":"200000",      
        "RequestMethod":"QUERY",      
        "ResponseFormat":"JSON"   
    }
}

Bulk Extract RETRIEVE request


To send Bulk Extract request using the RETRIEVE method, issue a POST /rest/2.0/bulk\_api\_request call with the following parameters:

FieldData TypeDescription
RequestNameTEXTDescriptive name of the new request.
RequestBodyTEXTAREAWhen applying the RETRIEVE method, this parameter can be a standard ANSI SQL query, for example Id>2.
RecordsPerBatchNUMBERNumber of records returned in one batch. For example, prior to running a request that is bound to return about a million records, consider splitting this amount into smaller batches of 50 thousand records each for easier processing.
RequestMethodTEXTRETRIEVE
ResponseFormatTEXTCSV
RetrieveEntityNameTEXTThe name of the target entity. Make sure to indicate the internal entity name as it appears in Setup > Development > Entities > [entity details] > Entity Name. This name cannot include spaces or start with a number or punctuation mark.
CSVDelimiterTEXTThe character used as the column-delimiter in a CSV file.
CSVQualifierTEXTThe characters placed around each field to signify that it is the same field. Most commonly those are double quotes (").
CSVEndLineFormatTEXTThe character used to signify the end of line in a CSV file: CR, LF or CRLF.

Sample Bulk Extract RETRIEVE request:

URL: https://my.billingplatform.com/myorg/rest/2.0/bulk\_api\_request

Method: POST

{
"brmObjects":
{
    "RequestName": "RetrieveCsvExample",
    "RequestBody":"Id > 2",
    "RecordsPerBatch":"1000000",
    "RetrieveEntityName":"ACCOUNT",
    "RequestMethod":"RETRIEVE",
    "ResponseFormat":"CSV",
    "CSVDelimiter":";",
    "CSVQualifier":"\"",
    "CSVEndLineFormat":"CRLF"
}
}

Response

A successful POST request returns the ID assigned to the new request. You can use this ID to search for the request in the Setup > Data Management > Bulk API Request section in the BillingPlatform console.

{
"createResponse": [
    {
    "ErrorCode": "0",
    "ErrorText": " ",
    "ErrorElementField": " ",
    "Id": "number"
    
    }
]
}

As soon as the request status changes to PROCESSED, opening it in the Setup > Data Management > Bulk API Request section displays its details split into four categories:

  • Request Specification: The request settings provided in the API call. In addition, this category includes the Number of batches field that represents the total number of batches retrieved by the request.

  • Processing Log: The total number of processed records and the request processing log. The log contains details about how the request was parsed, when its processing started, whether batch execution was successful, etc.

  • Results: The data retrieved by the request split into batches in the required format. You can click on each batch to view its details and download it (see also Direct file download).

  • System Information: The request ID, the date and time of its creation, etc.

Data Types

BillingPlatform allows you to create new entities using existing data types. You can also alter some of the standard ones in the system.

Data types allow you to store any data in BillingPlatform in any way you need to. They enable you to customize the way your customers enter data, the way data is displayed in list and detail views, and how they search for information.

Data TypeDescription
CHECKBOXCheckbox is a small box that when selected enables a particular feature or a particular option.

This is recommended for binary data or an enable/disable option.

For example:

checkbox

CURRENCYCurrency data type automatically formats the field as an amount with a currency symbol.

For example, $500 for 500 US dollar or €100 or 100 Euro.

The user can select among the list of available currency symbols in the system.
DATA_SELECTORDate Selector allows the user to enter a date or pick a date from a popup calendar.

The following format is currently accepted by BillingPlatform: YYYY-MM-DD
DATE_TIME_SELECTORDate and Time Selector allows the user to specify the date, time, and time zone of some event in the system:
date time selector The time will be automatically adjusted to account for daylight savings based on the selected time zone.
DIVIDERDivider adds a horizontal dividing line on the user interface.
DOCUMENTDocument data type allows the user to upload files that can be downloaded at a later time. This is useful for uploading contracts, images, or any type of supplementary file the user may need.
(The Document data type can also be populated using workflows or through the API.)
EXTENSION_WIDGETExtension widget offers a user a place to put a JavaScript program within the page. JavaScript is an object-oriented computer programming language commonly used to create interactive effects within web browsers. It allows users to define behaviors far more capable and complex compared to other data types.
For example,the Quantity Change button is an extension widget. The image below is where that button is configured. BillingPlatform has a list of extension widgets the user can choose from.
Go to Setup > Develop > Extension Widgets.
FORMULAFormula is a virtual data type that does not exist as a field in the database. The value of the field is generated dynamically whenever operations need to access it such as UI rendering, reports, workflows, workflow actions, and API information retrieval.
Since the value is generated dynamically, the field also has the following behaviors:

  • The field cannot be assigned a value when creating or updating an entity record through the UI.
  • When manipulating records through the API, updates, inserts, and upserts are not allowed. The Retrieve command is allowed.
LABELLabel is a read-only text information field. This is useful for displaying information on the user interface.
For example:

Ecom Gateway ID and Ecom Name are both Labels. They are read-only information. They don’t have an editable field.
LOOKUPLookup allows users to choose from predefined values. For example, a Lookup on an account entity only displays existing accounts. The user can only choose from this list.
BillingPlatform allows the user to reference other system and custom entities when configuring this data type. Nested records are also allowed.
NUMBERNumber data type allows the user to enter a decimal number with up to 38 significant digits in the range of -(10125) to +(10125).

maximum value - 999...(38 9's) x10125

Can be represented to full 38-digit precision (the mantissa).

minimum value - 999...(38 9's) x10125

Can be represented to full 38-digit precision (the mantissa).

RADIO_GROUP_HORIZONTALA Radio button is an icon representing one of a set of options, only one of which can be selected at a time. The position of the radio button group is horizontal in this data type.
RADIO_GROUP_VERTICALA Radio button is an icon representing one of a set of options, only one of which can be selected at a time. The position of the radio button group is vertical in this data type.
RELATED LISTRelated list allows the user to relate multiple records between entities that have existing parent-child or reference relationships to a single record.
For example, a list of invoice line items on the invoice page is done by configuring the Related List on the invoice page.
To know more about Related List, refer to this link.
SELECT1Select1 is for drop-down menus where the user can choose from a list of options. This is recommended if there are more than 2 options to choose from. The Select1 date type only allows the user to choose one value.

For example, here you can select one Type Context from the list of options.

TEXTText field stores text data (UTF-8).
TEXT AREAText area allows the user to enter MORE than 4000 characters. This allows the user more room for description or explanation.
TEXT_AREA_SMALLText area allows the user to enter LESS than 4000 characters.

REST Methods

The REST API allows you to bypass the need to download a WSDL and access the resources and methods available in the system directly. This approach uses the standard web methods such as GET, PUT, and POST in order to retrieve, create, update, and delete records from the different BillingPlatform entities and objects as needed.

In general, the resources are accessible using the following format:

https://BillingPlatform domain/org/rest/2.0/resource

Where:

  • BillingPlatform domain - URL of the org such as my.billingplatform.com or sandbox.billingplatform.com.
  • org - Your organization's name
  • resource - REST resource, such as the entity name or LOGIN/LOGOUT.

The BillingPlatform REST API supports the `GetMySessionID`` function both in regular callout sessions and from within workflow actions.

Message bodies for single record manipulation

Message bodies where needed are written in JSON format, such as the example message body for a POST (equivalent to CREATE in CRUD/SOAP terms) function call against the ACCOUNT entity:

{
"brmObjects":{
"Name":"My New Rest Account",
"Status":"ACTIVE",
"AccountType":"Required"
}
}

Message bodies for multiple records manipulation

The majority of the BillingPlatform API functions (Create, Upsert, Update, Delete, etc.) allow you to manipulate multiple records in one API call, typically by passing multiple records of type brmObject. This is done by containing the collection of brmObjects into an array. For example, if we bulk up the Web Service call above to create more than one Account record, the following JSON message is used:

{
"brmObjects":[
    {
        "Name":"My New REST Account 01",
        "Status":"ACTIVE",
        "AccountType":"Required"
    },
    {
        "Name":"My New REST Account 02",
        "Status":"ACTIVE",
        "AccountType":"Required"
    },
    {
        "Name":"My New REST Account 03",
        "Status":"ACTIVE",
        "AccountType":"Required"
    },
    {
        "Name":"My New REST Account 04",
        "Status":"ACTIVE",
        "AccountType":"Required"
    }
]
}

This will create multiple Account records using only one API function call.

Login

This request is necessary in order to obtain a session identification number from BillingPlatform that will be required for all REST requests. Login is a POST request that requires no headers and the login parameter is required in the URL. The session is configured to time out after 2 hours of inactivity.

Sample REST Login request URL: https://my.billingplatform.com/myorg/rest/2.0/login

Method: POST

{
"username":"my.username",
"password":"password"
}

Sample REST Login response

{
"loginResponse": [
    {
        "SessionID": "qIYsMgFsRbbXRUlxaLnTFWPxbdfCiYabkmBGvzgj",
        "ErrorCode": "0",
        "ErrorText": []
    }
]
}

Logout

The method to terminate the active Web Service session. When invoked, it will invalidate the session ID obtained from the Login method. Logout is a POST request that requires the sessionid as part of the header. The logout parameter is required in the URL.

Sample REST Logout Request

URL: https://my.billingplatform.com/myorg/rest/2.0/logout

Method: POST

(Header Parameters)

NameValue
sessionidSession ID obtained from the LOGIN method.

Query

Query requests will add the URL-Encoded Query Statement to the “sql” parameter like so:

https://sandbox.billingplatform.com/<yourOrg>/rest/2.0/query?sql=SELECT+Status%2C+Sum%28GrandTotalAmount%29+as+total++FROM+Invoice++WHERE+BillingProfileObj.AccountObj.Name+like+%27%25Blue%25%27+GROUP+BY+Status     

Responses will be returned in JSON format.

Query Results Query Results come in two basic types:

  1. Hierarchical List of Objects for standard queries
  2. List of Name-value pairs for Queries using aggregate functions

For more information please see the BillingPlatform Query Language

Sample REST Query Response

{
"queryResponse": [
    {
        "Id": null,
        "Status": "CLOSED",
        "TOTAL": "25024"
    },
    {
        "Id": null,
        "Status": "CURRENT",
        "TOTAL": "101"
    }
]
}

When using the Query API, it is important to note that the column alias used in a query should not have the same name as another column in the same entity. If the column alias is the same as another column, the query will produce unexpected results.

For example, consider the following queries (PL/SQL):

SELECT id, acme_due_date FROM Invoice WHERE id = 613715;
SELECT id, acme_due_date AS DueDate FROM Invoice WHERE id = 613715;

In these queries, the second query uses the alias DueDate for the acme_due_date column. However, DueDate is also the name of another column in the Invoice entity. Therefore, the second query will return data from the DueDate column instead of the acme_due_date column, resulting in unexpected JSON output.

To avoid this issue, ensure that the column aliases used in your query are unique and do not conflict with any other column names in the same entity.

Undelete

The Undelete method is used to restore records within your instance of the enterprise billing system that have been sent to the recycle bin.

Undelete is an UNDELETE resource that requires the sessionid in the header. The entity for update is passed through the URL.

Sample REST Undelete Request (Account Entity)

URL: https://my.billingplatform.com/myorg/rest/2.0/undelete/account

Method: DELETE

(Header Parameters)

NameValue
sessionidSession ID obtained from the LOGIN method.
{
"Id":"27417"
}

Sample REST Undelete Response:

{
"deleteResponse": [
{
    "ErrorCode": "0",
    "ErrorText": " ",
    "Id": "27417",
    "Success": "true"
}
]
} 

File Upload

BillingPlatform allows you to upload files to entities. You may use the file upload method to upload files that have been Base64 encoded to a field on any object of type "FILE".

File Upload is a POST request that requires the sessionid in the header. The entity, record Id and field where the file will be uploaded to is passed through the URL.

Sample REST File Upload Request (Account Entity)

URL: https://my.billingplatform.com/myorg/rest/2.0/files/account/27471/DOCUMENT

Where:

  • account - Entity name where the file will be coming from.
  • 27471 - Record ID that contains the file that you need.
  • DOCUMENT - Field that houses the file. In this example, the DOCUMENT field contains the file. This can be different depending on the entity configuration.

Method: POST

(Header Parameters)

NameValue
sessionidSession ID obtained from the LOGIN method.
{

"Id":"Number",
"EntityName":"Name_of_Entity",
"FileName":"Document.txt",
"FieldName":"Some_File",
"Data":"dGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRl"
}

Sample REST File Upload Response:

{
"file_uploadResponse": [
    {
        "ErrorCode": "0",
        "ErrorText": " ",
        "ErrorElementField": " "
    }
    ]
}

File Download

You can download files using the file download method. Files downloaded will be Base64 encoded and you must use a Base64 decoder in order to convert the downloaded file.

File Download is a GET request that requires the sessionid in the header. The entity, record Id and field name where the file will be downloaded from is passed through the URL.

Sample REST File Download Request (Account Entity)

URL: https://my.billingplatform.com/myorg/rest/2.0/files/account/27471/DOCUMENT

Where:

  • account - Entity name where the file will be coming from.
  • 27471 - Record ID that contains the file that you need.
  • DOCUMENT - Field that houses the file. In this example, the DOCUMENT field contains the file. This can be different depending on the entity configuration.

Method: GET

(Header Parameters)

NameValue
sessionidSession ID obtained from the LOGIN method.

Sample REST File Download Response:

{
"file_downloadResponse": [
{
    "ErrorCode": "0",
    "ErrorText": " ",
    "ErrorElementField": " ",
    "Data": "dGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhp\ncyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBp\ncyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBh\nIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRl\nc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3Qg\ndGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhp\ncyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBp\ncyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBh\nIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRl\nc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3Qg\ndGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhp\ncyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBp\ncyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBh\nIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRl\nc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3Qg\ndGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhp\ncyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBp\ncyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBh\nIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRl\nc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3Qg\ndGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhp\ncyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBp\ncyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBh\nIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRl\nc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3Qg\ndGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhp\ncyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBp\ncyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBh\nIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRlc3QgdGhpcyBpcyBhIHRl\n"
}
]
}