What is RdWebApi?

Nuget allows .Net code to be shared between applications. When it comes to EnergyLink APIs the need arose internally to use a Nuget package to help manage API calls between EnergyLink and those applications. We also used the package to write automated tests for those interactions.

With many EnergyLink clients, vendors and 3rd parties now looking to use Web APIs, we decided to take it a step further and offer RdWebApi externally. We see many benefits to this approach:

  • The package takes care of the technical details (networking, serialization, compression) so you can focus on the business logic and spend less time up front
  • Inputs and outputs use strongly-typed .Net classes
  • Fixes and enhancements can be acquired quickly and easily (typically in one click)
  • Access the system the same way we do internally, ensuring your scenario is well supported (you literally use the same code as EnergyLink)
  • Some RPA Software solutions can import a Nuget package or at least the .dll file

Requirements

  • All interfaces require an active EnergyLink service account (one account per full-service company)
  • Certain roles and/or features may be required depending on usage. Contact EnergyLink Support to get set up
  • A .Net-compatible environment (e.g. Framework 4.0+, Core 2.0+, Windows Powershell, certain RPA Software)
  • The Newtonsoft.Json package is a required dependency. For .Net Framework versions below 4.7.1, System.IO.Compression is required as well.
  • Some methods are not included when using .Net Framework 4.0. Please consider upgrading to 4.5+

Where to get it

The package is available via a public Nuget repository for any full-service client. Please contact us to get the link. We would be happy to discuss your project and help you find the best solution.

In scenarios where Nuget is unsupported and a .DLL file is required, the .DLL must be extracted from the .Nuget file. .Nuget files are zip files so just change the extension to ".zip" and browse the file for the appropriate .DLL.

How to use it

Each category of APIs (Operated File Uploads, Non-Operated File Downloads, Non-Operated Automation) has a different 'EnergyLinkClient' class. Once this has been identified, most Web API calls take two lines of code, e.g.:

		
using (var client = new EnergyLinkClientUpload(
    environment: EnergyLinkEnvironment.Test,
    loginName: "USERID",
    password: "PASSWORD")
)
{
    var result = await client.UploadJibXmlAsync(inputStream, "Invoices.xml");
}
            
        

Examples are provided for each Web API call elsewhere on this page

Integrating with RPA software

Some clients have expressed an interest in calling Web APIs from commercially available RPA software. Many of these products can integrate with the RdWebApi package - details for some of these products are noted below. Please contact us if you'd like to discuss your RPA project.

Alteryx

While Nuget packages cannot be imported directly, Alteryx API can be called from .Net code using the information here. Several basic examples are available on the Alteryx forums.

Automation Anywhere

While Nuget packages cannot be imported directly, .Net DLLs can be used by custom metabots. For information see this example video.

Blue Prism

Blue Prism can reference a custom .Net DLL but we have not yet worked with a client using this product.

MuleSoft

MuleSoft has a .Net Connector for importing .Net DLLs. RdWebApi could be imported directly, or a custom project can be created which references RdWebApi.

Selenium

Selenium is used purely for web browser automation. Web APIs are a much better approach (particularly if the RdWebApi package is an option).

UI Path

Great news! UI Path is able to import Nuget packages and works with RdWebApi. Using Windows Workflow Foundation Activities, each Web API call can be integrated right into your project. You can use our activities included in the package, or reference RdWebApi from your own .Net project and create your own activities. Please contact us for a detailed explanation on how to set this up inside UI Path.

What is EnergyNode?

Ever wonder why EnergyLink doesn't just interact directly with your accounting and land systems to transfer data? The main reason is a security one - industry standard practice is to deny all incoming connections unless they match a very specific rule. These rules are tedious to manage as your company and its infrastructure evolve.

EnergyNode solves this problem by running within your company where it can speak directly to your server(s). It acts as a broker between your systems and EnergyLink using an outgoing connection, similar to how your phone handles email and notifications. This enables two-way communication with minimal maintenance.

EnergyNode is...

Secure
  • Uses the same secure communications as your web browser
Easy
  • Installs into any Windows-based system with the .Net Framework installed
  • Can be managed remotely - IT involvement should not be required after initial setup
Flexible
  • Functionality is modular - only install the modules for the systems you use and the functions you need
  • Tasks can be scheduled or triggered manually right from EnergyLink
  • Can run anywhere (e.g. Azure/AWS) if the necessary network access (e.g. VPN) is in place
Always getting better
  • Logging data is sent to EnergyLink for our staff to troubleshoot
  • The service and modules update automatically
  • New modules are easy to add. Contact us today to find out if your system is supported

Operated File Uploads

Data files may be uploaded to EnergyLink using one of several interfaces. Successful transmission does not indicate that the data was processed and validated successfully, which can take time. Emails can be configured to notify the sender whether or not a file was successfully processed.

A User ID and Password will be provided by EnergyLink Support. This is a service account only and cannot log into the website using a web browser. A separate user account can be provided if you need to log into the site for testing purposes. Each interface is available in both the Production and Demo environments but a separate service account is used in each.

SFTP Recommended for Clients

  • Connect to
    • Production: sftp.energylink.com, port 22 (SFTP)
    • Demo: sftp-demo.energylink.com, port 22 (SFTP)
  • Zipping files before upload is highly recommended.
  • Several types of files are accepted:
    • JIB CSV - This is the default. Simply upload the ZIP or CSV file, the accounting month will be determined from the file contents.
    • JIB XML - Specify by changing to the 'JIBXML' directory. Simply upload the ZIP or XML file, the accounting month will be determined from the file contents.
    • Revenue Check Detail
      • Specify by changing to the 'REVENUE' directory and/or uploading a filename which includes the '^' character.
      • For file formats requiring the Operated Org number, specify filename as ORGNUMBER^yyyyMM.ext (e.g. 0025^201308.zip)
        • If the '^' character is not supported you may use '_' instead but then must change to the 'REVENUE' directory as noted above.
      • Accounting Month is specified using the yyyyMM above. If not provided, the current month is used.
      • If it is not possible to use the above syntax we can work with you to implement custom rules for your account(s).
    • AR Balances - Specify by changing to the 'AR' directory before uploading the file.
    • Desk Audit Land Extract - Specify by changing to the 'DESKAUDIT' directory before uploading the file.
    • Void Checks - Specify by changing to the 'VOIDCHECK' directory before uploading the file. Contact EnergyLink Support for details on the required file format.

Web API Recommended for Vendors

When using REST, the Authentication header is always required as follows:
  • Concatenate the User ID and Password using a colon, e.g. "USERID:PASSWORD"
  • Base-64 encode this string, e.g. "VVNFUklEOlBBU1NXT1JE==". This can be done here.
  • Use Basic Authentication in the "Authorization" header, e.g. "Basic VVNFUklEOlBBU1NXT1JE=="
  • Note: If testing in testing environment use https://api-demo.energylink.com in the URL

When using RdWebApi an instance of EnergyLinkClientUpload is always required, e.g.

		
using (var client = new EnergyLinkClientUpload(
    environment: EnergyLinkEnvironment.Test,
    loginName: "USERID",
    password: "PASSWORD")
)
{
    // Your API calls go here. See examples below.
}
            
        
RdWebApi will automatically zip files for upload if the filename does not end with .zip

All APIs return JSON with the following fields. RdWebApi methods return an UploadResult object with them.

Element Name Element Type Example Note
StatusCode integer 200
Message string The account provided does not have operated upload access.
ProcessId long 123
BatchId long? 456
POST https://api.energylink.com/Api/Upload/JibXml

JibXml

Request

var result = await client.UploadJibXmlAsync(
	stream,	//e.g. MemoryStream, FileStream, OracleBlob
	"file.xml",
	new DateTime(2018, 11, 30, 12, 0, 0, DateTimeKind.Local)
);
    
	
Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
FileName file.zip
AccountingMonth 2018-11-30

Request body is the file data.

POST https://api.energylink.com/Api/Upload/JibCsv

JibCsv

Request

var result = await client.UploadJibCsvAsync(
	stream,	//e.g. MemoryStream, FileStream, OracleBlob
	"file.xml",
	new DateTime(2018, 11, 30, 12, 0, 0, DateTimeKind.Local)
);
    
	
Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
FileName file.zip
AccountingMonth 2018-11-30

Request body is the file data.

POST https://api.energylink.com/Api/Upload/Revenue

Revenue

Request

var result = await client.UploadRevenueAsync(
	stream,	//e.g. MemoryStream, FileStream, OracleBlob
	"file.xml",
	new DateTime(2018, 11, 30, 12, 0, 0, DateTimeKind.Local),
	"ORG1", 
	"EFT" // Some file formats include Org Num and Payment Type in the file, otherwise include it here
);
    
	
Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
FileName file.zip
AccountingMonth 2018-11-30
OpOrgNum ORG1
PaymentType ACH

Request body is the file data.

POST https://api.energylink.com/Api/Upload/VoidCheck

VoidCheck

Request

var result = await client.UploadVoidCheckAsync(
	stream,	//e.g. MemoryStream, FileStream, OracleBlob
	"file.xml"
);
    
	
Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
FileName file.zip

Request body is the file data.

POST https://api.energylink.com/Api/Upload/DeskAuditXml

DeskAuditXml

Request

var result = await client.UploadDeskAuditXmlAsync(
	stream,	//e.g. MemoryStream, FileStream, OracleBlob
	"file.xml"
);
    
	
Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
FileName file.zip

Request body is the file data.

POST https://api.energylink.com/Api/Upload/Ar

Ar

Request

var result = await client.UploadArBalancesAsync(
	stream,	//e.g. MemoryStream, FileStream, OracleBlob
	"file.xml"
);
    
	
Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
FileName file.zip

Request body is the file data.

Note: Web Services are still supported but we now recommend using Web APIs

Web Service

  • The IP address(es) or range(s) must first be whitelisted in EnergyLink. Please provide these to EnergyLink when setting up the service account. These may differ between the Production and Demo environments
  • The production website is at api.energylink.com.
    The demo website is at api-demo.energylink.com.
  • Several methods are defined here depending on file upload format. Click for samples of the SOAP request/response format. The full WSDL is under the Service Description link. The 'Authorize' method is not required.
  • The EnergyLink public SSL certificate can be found here.
  • Samples are available:

Web API

EnergyLink is now offering Web APIs to retrieve operated AR information.

A User ID and Password will be provided by EnergyLink Support. This is a service account only and cannot log into the website using a web browser. A separate user account can be provided if you need to log into the site for testing purposes. Each interface is available in both the Production and Demo environments but a separate service account is used in each.

When using REST, the Authentication header is always required as follows:
  • Concatenate the User ID and Password using a colon, e.g. "USERID:PASSWORD"
  • Base-64 encode this string, e.g. "VVNFUklEOlBBU1NXT1JE==". This can be done here.
  • Use Basic Authentication in the "Authorization" header, e.g. "Basic VVNFUklEOlBBU1NXT1JE=="
  • Note: If testing in testing environment use https://api-demo.energylink.com in the URL
POST https://api.energylink.com/Api/v1/Ar/GetManagementReportOrgSummary

GetManagementReportOrgSummary

Queries the results of the Org Summary tab on the existing AR Subledger XLS Management report.

Request

using (var client = new EnergyLinkClientOp(
	environment: EnergyLinkEnvironment.Test,
	loginName: "USERID",
	password: "PASSWORD")
)
{
	var result = client.GetManagementReportOrgSummaryAsync(
		new DateTime(2023, 11, 1),
		new DateTime(2024, 8, 1)
	);
}

	
Response

All values are automatically deserialized into .Net classes within the ArManagementDataElement class. Happy coding!

Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
Accept text/xml
Request JSON Name Sample Value Note
From
"2024-08-23"
To
"2024-11-21"
InvoiceTypes
optional
["JIB"]
ArRiskCode
optional
["WriteOff","Offset"]
OperatorDisputeStatuses
optional
["Interdept","PrtAccept"]
MyBa
optional
"My Company"
OrgNum
optional
"1111"
InvoiceNumber
optional
"12345"
InvoiceNumberExactSearch
optional
true
ShowToleranceMonths
optional
3.0m
ArAccount
optional
"100.10"
PartnerName
optional
"Partner Name"
Response

See the ArManagementData XML schema for details on the response body for this call. The response will be in either XML or JSON depending on the 'Accept' header specified but the structure and content will be the same.

Non-Operated File Downloads

The following methods may be used to move 'Created' vouchers and their related files from EnergyLink to the client's environment. Upon download the status will change to 'Downloaded' and the voucher will no longer be returned in the 'Vouchers for Download' queries. A voucher can be set from 'Downloaded' back to 'Created' by manually re-creating it via the website.

By default users must create vouchers using the EnergyLink website. In some scenarios vouchers can be created automatically as data is received (e.g. if only operator coding is required).

Numerous additional options exist, depending on file format (e.g. CDEX). Please contact us to discuss your specific requirements.

For information about automating the steps to process an invoice/check and create the voucher (e.g. using RPA software), see the Non-Operated Automation section.

SFTP / FTP (Push) Recommended for Clients

EnergyLink will transmit files to a location provided by the client as they become available.

  • Destination Address, User ID and Password must be provided by the client (this may differ between Production and Demo).
  • Consolidated CDEX - For CDEX files only, all files may be grouped into a single transmission sent once per day.
  • Whitelist the following IP addresses:
    • 161.38.183.10
    • 199.231.63.10
  • The destination folder may be monitored by a scheduled job or certain enterprise software for upload into the client's system.
  • Failed transfers will be retried 4 times, 15 minutes apart before giving up. Unsuccessful transfers may be found under 'Non-Operated Voucher Search'. A failed voucher may be recreated to trigger a new transfer.

SFTP (Pull)

Clients may connect to the EnergyLink FTP site periodically to list and download new files.

A User ID and Password will be provided by EnergyLink Support. This is a service account only and cannot log into the website using a web browser. A separate user account can be provided if you need to log into the site for testing purposes. Each interface is available in both the Production and Demo environments but a separate service account is used in each.

  • Connect to
    • Production: sftp.energylink.com, port 22 (SFTP)
    • Demo: sftp-demo.energylink.com, port 22 (SFTP)
  • JIBs - Change to the 'DOWNLOAD_JIB' directory.
    Revenue Check Detail - Change to the 'DOWNLOAD_REVENUE' directory.
  • Downloaded files will be automatically removed from the directory listing. They may be recreated via the website to make them reappear for download.

Web API Polling Recommended for Vendors

A User ID and Password will be provided by EnergyLink Support. This is a service account only and cannot log into the website using a web browser. A separate user account can be provided if you need to log into the site for testing purposes. Each interface is available in both the Production and Demo environments but a separate service account is used in each.

When using REST, the Authentication header is always required as follows:
  • Concatenate the User ID and Password using a colon, e.g. "USERID:PASSWORD"
  • Base-64 encode this string, e.g. "VVNFUklEOlBBU1NXT1JE==". This can be done here.
  • Use Basic Authentication in the "Authorization" header, e.g. "Basic VVNFUklEOlBBU1NXT1JE=="
  • Note: If testing in testing environment use https://api-demo.energylink.com in the URL
GET https://api.energylink.com/Api/Voucher/GetInvoiceVouchersForDownload

GetInvoiceVouchersForDownload

Request

using (var client = new EnergyLinkClientVoucher(
	environment: EnergyLinkEnvironment.Test,
	loginName: "USERID",
	password: "PASSWORD")
)
{
	var result = await client.GetInvoiceVouchersForDownloadAsync();
}
	
	
Response

All values are automatically deserialized into .Net classes within the VouchersElement class. Happy coding!

Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
Accept text/xml
InvoiceType
optional
JIB
Response

The response will be in either XML or JSON depending on the 'Accept' header specified but the structure and content will be the same.


{
    "Vouchers": {
        "XmlSchemaVersion": "2018-11-22",
        "Header": {
            "RequestLoginName": "User1",
            "RequestUserId": 1234,
            "RequestBaId": 5678,
            "RequestDate": "2019-01-01T01:02:03.0000000-06:00",
            "RequestMethod": "VouchersForDownloadXml"
        },
        "Vouchers": [
            {
				"VoucherId": 1122334,
				"VoucherReference": "VCH_REFER",
				"VoucherAccountingMonth": "2016-01-01T00:00:00",
				"DocType": "JIB",
				"VoucherAmount": 1234.56,
				"FileName": "1122334_VCH_REFER_5555_201601.xml",
				"FileSize": 123456,
				"ActionTypeDescription": "Voucher",
				"FirstProcessedUserId": 654321,
				"FirstProcessedUserName": "User2",
				"FirstProcessedDate": "2015-12-31T00:00:00.000000",
				"FirstDownloadedUserId": 654321,
				"FirstDownloadedUserName": "User2",
				"FirstDownloadedDate": "2015-12-31T00:05:00.000000",
				"LastProcessedUserId": 654321,
				"LastProcessedUserName": "User2",
				"LastProcessedDate": "2019-01-01T00:00:00.000000",
				"LastDownloadedUserId": 555555,
				"LastDownloadedUserName": "User3",
				"LastDownloadedDate": "2019-02-01T00:00:00.000000",
				"VoucherInvoices": [
					{
					"InvoiceId": 22222,
					"InvoiceNumber": "0123456789012345",
					"PlVersion": 0,
					"VoucherInvoiceAmount": 1234.56
					}
				]
            }
        ]
    }
}
	
	
GET https://api.energylink.com/Api/Voucher/GetPaymentVouchersForDownload

GetPaymentVouchersForDownload

Request

using (var client = new EnergyLinkClientVoucher(
	environment: EnergyLinkEnvironment.Test,
	loginName: "USERID",
	password: "PASSWORD")
)
{
	var result = await client.GetPaymentVouchersForDownloadAsync();
}
	
	
Response

All values are automatically deserialized into .Net classes within the VouchersElement class. Happy coding!

Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
Accept text/xml
Response

The response will be in either XML or JSON depending on the 'Accept' header specified but the structure and content will be the same.


{
    "Vouchers": {
        "XmlSchemaVersion": "2018-11-22",
        "Header": {
            "RequestLoginName": "User1",
            "RequestUserId": 1234,
            "RequestBaId": 5678,
            "RequestDate": "2019-01-01T01:02:03.0000000-06:00",
            "RequestMethod": "VouchersForDownloadXml"
        },
        "Vouchers": [
            {
				"VoucherId": 1122334,
				"VoucherReference": "VCH_REFER",
				"VoucherAccountingMonth": "2016-01-01T00:00:00",
				"DocType": "Revenue",
				"VoucherAmount": 1234.56,
				"FileName": "1122334_VCH_REFER_5555_201601.xml",
				"FileSize": 123456,
				"ActionTypeDescription": "Voucher",
				"FirstProcessedUserId": 654321,
				"FirstProcessedUserName": "User2",
				"FirstProcessedDate": "2015-12-31T00:00:00.000000",
				"FirstDownloadedUserId": 654321,
				"FirstDownloadedUserName": "User2",
				"FirstDownloadedDate": "2015-12-31T00:05:00.000000",
				"LastProcessedUserId": 654321,
				"LastProcessedUserName": "User2",
				"LastProcessedDate": "2019-01-01T00:00:00.000000",
				"LastDownloadedUserId": 555555,
				"LastDownloadedUserName": "User3",
				"LastDownloadedDate": "2019-02-01T00:00:00.000000",
				"VoucherInvoices": [
					{
					"InvoiceId": 22222,
					"InvoiceNumber": "0123456789012345",
					"VoucherInvoiceAmount": 1234.56
					}
				]
            }
        ]
    }
}
	
	
GET https://api.energylink.com/Api/Voucher/GetVoucherFile/{id:long}

GetVoucherFile

Usually you will need to call GetInvoiceVouchersForDownload first to get the Voucher Id.

Request

using (var client = new EnergyLinkClientVoucher(
	environment: EnergyLinkEnvironment.Test,
	loginName: "USERID",
	password: "PASSWORD")
)
{
	var result = await client.GetVoucherFileStreamAsync(123);
}
	
	
Response

All values are automatically deserialized into .Net classes within the VoucherFile class. Happy coding!

Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
Response

Returns the data file for the voucher using Content-Type application/octet-stream. Actual format of the file depends which voucher type the client is using (e.g. CSV, TXT, XLSX). For filename and size, see the Content-Disposition header.

GET https://api.energylink.com/Api/Voucher/GetVoucherReports/{id:long}

GetVoucherReports

Usually you will need to call GetInvoiceVouchersForDownload first to get the Voucher Id.

Once EnergyLink enables the PDF option, the PDF backup is automatically created with each voucher. The GetVoucherReports Web API call will fetch these reports.

Request

using (var client = new EnergyLinkClientVoucher(
	environment: EnergyLinkEnvironment.Test,
	loginName: "USERID",
	password: "PASSWORD")
)
{
	var result = await client.GetVoucherReportsAsync(123);
}
	
	
Response

All values are automatically deserialized into .Net classes within the VoucherFile class. Happy coding!

Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
Response

Returns the PDF files for the voucher using Content-Type multipart (where each part is one PDF using Content-Type application/octet-stream). For filename and size, see the Content-Disposition header of each part.

Note: Web Services are still supported but we now recommend using Web APIs

Web Service Polling

A User ID and Password will be provided by EnergyLink Support. This is a service account only and cannot log into the website using a web browser. A separate user account can be provided if you need to log into the site for testing purposes. Each interface is available in both the Production and Demo environments but a separate service account is used in each.

  • The IP address(es) or range(s) must first be whitelisted in EnergyLink. Please provide these to EnergyLink when setting up the service account. These may differ between the Production and Demo environments.
  • The production website is at api.energylink.com.
    The demo website is at api-demo.energylink.com.
  • Clients may connect to a set of EnergyLink Web Services periodically to list (GetVouchersForDownload) and download new files from the list (GetVoucherFile).
    • Passing TestMode=true to GetVouchersForDownload will return a sample JIB voucher (VoucherID=-1) for testing, regardless of what data is currently available for your User ID.
    • Passing VoucherID=-1 to GetVoucherFile will return a sample JIB voucher file in an XML format. The content of this file is just a sample - when VoucherID is not -1 the file returned will be in the format configured for your company.
  • See the definitions for GetVouchersForDownload and GetVoucherFile here. The full WSDL is under the Service Description link. The methods may be invoked from a web browser for easy testing.
  • Downloaded files will be automatically removed from the list. They may be recreated via the website to make them reappear for download.
  • The EnergyLink public SSL certificate can be found here.
  • Sample code using PowerShell is available here.

Non-Operated Automation

EnergyLink offers a rich set of Web APIs for retrieving information and automating workflow. The RdWebApi package is strongly recommended for the latter.

A User ID and Password will be provided by EnergyLink Support. This is a service account only and cannot log into the website using a web browser. A separate user account can be provided if you need to log into the site for testing purposes. Each interface is available in both the Production and Demo environments but a separate service account is used in each.

When using REST, the Authentication header is always required as follows:
  • Concatenate the User ID and Password using a colon, e.g. "USERID:PASSWORD"
  • Base-64 encode this string, e.g. "VVNFUklEOlBBU1NXT1JE==". This can be done here.
  • Use Basic Authentication in the "Authorization" header, e.g. "Basic VVNFUklEOlBBU1NXT1JE=="
  • Note: If testing in testing environment use https://api-demo.energylink.com in the URL

The first step is to search for documents and download their details. EnergyLink primary key values will be included with this data - store and refer to these keys when making subsequent Web API calls. See the Samples section for examples of multiple Web APIs used together to complete more complex tasks.

POST https://api.energylink.com/Api/NonOpAutomation/InvoiceSearch

InvoiceSearch

Queries high-level information about multiple invoices. For statement-level and detail-level information see Invoice.

Request

using (var client = new EnergyLinkClientNonOp(
	environment: EnergyLinkEnvironment.Test,
	loginName: "USERID",
	password: "PASSWORD")
)
{
	var nonOpInvoices = await client.GetInvoiceSearchAsync(
		Enums.DateSearchType.RECEIVED_DATE,
		new DateTime(2024, 11, 18, 12, 0, 0, DateTimeKind.Local),
		invoiceType: new[] { Enums.InvoiceTypes.JIB }
	);
}
	
    
Response

All values from the NonOpInvoices XML schema are automatically deserialized into .Net classes within the NonOpInvoicesElement class. Happy coding!

Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
Accept text/xml
Request JSON Name Sample Value Note
DateSearchType
"LAST_PROCESSED_DATE"
From
"2024-11-18T00:00:00.0000000-07:00"
To
optional
"2024-11-21T00:00:00.0000000-07:00"
InvoiceType
optional
"JIB"
Status
optional
["AcceptedModified","AcceptedAsIs"]
Response

See the NonOpInvoices XML schema for details on the response body for this call. The response will be in either XML or JSON depending on the 'Accept' header specified but the structure and content will be the same.

POST https://api.energylink.com/Api/NonOpAutomation/RevenueSearch

RevenueSearch

USA only: Queries high-level information about multiple checks. For property-level and detail-level information see Revenue.

Request

using (var client = new EnergyLinkClientNonOp(
	environment: EnergyLinkEnvironment.Test,
	loginName: "USERID",
	password: "PASSWORD")
)
{
	var nonOpRevenue = await client.GetRevenueSearchAsync(
		Enums.DateSearchType.RECEIVED_DATE,
		new DateTime(2024, 11, 18, 12, 0, 0, DateTimeKind.Local)
	);
}
	
    
Response

All values from the NonOpRevenue XML schema are automatically deserialized into .Net classes within the NonOpRevenueElement class. Happy coding!

Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
Accept text/xml
Request JSON Name Sample Value Note
DateSearchType
"LAST_PROCESSED_DATE"
From
"2024-11-18T00:00:00.0000000-07:00"
To
optional
"2024-11-21T00:00:00.0000000-07:00"
Status
optional
["AcceptedModified","AcceptedAsIs"]
Response

See the NonOpRevenue XML schema for details on the response body for this call. The response will be in either XML or JSON depending on the 'Accept' header specified but the structure and content will be the same.

POST https://api.energylink.com/Api/NonOpAutomation/RoyaltySearch

RoyaltySearch

Canada only: Queries high-level information about multiple royalties. For property-level and detail-level information see Royalty.

Request

using (var client = new EnergyLinkClientNonOp(
	environment: EnergyLinkEnvironment.Test,
	loginName: "USERID",
	password: "PASSWORD")
)
{
	var nonOpRoyalties = await client.GetRoyaltySearchAsync(
		Enums.DateSearchType.RECEIVED_DATE,
		new DateTime(2024, 11, 18, 12, 0, 0, DateTimeKind.Local)
	);
}
	
    
Response

All values from the NonOpRoyalties XML schema are automatically deserialized into .Net classes within the NonOpRoyaltiesElement class. Happy coding!

Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
Accept text/xml
Request JSON Name Sample Value Note
DateSearchType
"LAST_PROCESSED_DATE"
From
"2024-11-18T00:00:00.0000000-07:00"
To
optional
"2024-11-21T00:00:00.0000000-07:00"
Status
optional
["AcceptedModified","AcceptedAsIs"]
Response

See the NonOpRoyalties XML schema for details on the response body for this call. The response will be in either XML or JSON depending on the 'Accept' header specified but the structure and content will be the same.

GET https://api.energylink.com/Api/NonOpAutomation/Invoice/{id:long}/{summarization:alpha?}

Invoice

Queries detailed information about a single invoice. For high-level information on multiple invoices see InvoiceSearch.

Request

using (var client = new EnergyLinkClientNonOp(
    environment: EnergyLinkEnvironment.Test,
    loginName: "USERID",
    password: "PASSWORD")
)
{
    var nonOpInvoices = await client.GetInvoiceAsync(
        123456,
        Enums.InvoiceSummarization.Detail
    );
}
    
    
Response

All values from the NonOpInvoices XML schema are automatically deserialized into .Net classes within the NonOpInvoicesElement class. Happy coding!

URL Parameters
Parameter Name Description
id:long The invoice ID that comes from the search result InvoiceSearch
summarization:alpha (Optional) Can be one of the following values:
  • Invoice
  • Statement
  • Detail
Note: If not specified, Detail is the default value used.
Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
Accept text/xml
Response

See the NonOpInvoices XML schema for details on the response body for this call. The response will be in either XML or JSON depending on the 'Accept' header specified but the structure and content will be the same.

GET https://api.energylink.com/Api/NonOpAutomation/Revenue/{id:long}/{summarization:alpha?}

Revenue

USA only: Queries detailed information about a single check. For high-level information on multiple checks see RevenueSearch.

Request

using (var client = new EnergyLinkClientNonOp(
    environment: EnergyLinkEnvironment.Test,
    loginName: "USERID",
    password: "PASSWORD")
)
{
    var nonOpRevenue = await client.GetRevenueAsync(
        123456,
        Enums.InvoiceSummarization.Detail
    );
}
    
    
Response

All values from the NonOpRevenue XML schema are automatically deserialized into .Net classes within the NonOpRevenueElement class. Happy coding!

URL Parameters
Parameter Name Description
id:long The invoice ID that comes from the search result RevenueSearch
summarization:alpha (Optional) Can be one of the following values:
  • Invoice
  • Statement
  • Detail
Note: If not specified, Detail is the default value used.
Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
Accept text/xml
Response

See the NonOpRevenue XML schema for details on the response body for this call. The response will be in either XML or JSON depending on the 'Accept' header specified but the structure and content will be the same.

GET https://api.energylink.com/Api/NonOpAutomation/Royalty/{id:long}/{summarization:alpha?}

Royalty

Canada only: Queries detailed information about a single royalty. For high-level information on multiple royalties see RoyaltySearch.

Request

using (var client = new EnergyLinkClientNonOp(
    environment: EnergyLinkEnvironment.Test,
    loginName: "USERID",
    password: "PASSWORD")
)
{
    var nonOpRoyalty = await client.GetRoyaltyAsync(
        123456,
        Enums.InvoiceSummarization.Detail
    );
}
    
    
Response

All values from the NonOpRoyalties XML schema are automatically deserialized into .Net classes within the NonOpRoyaltiesElement class. Happy coding!

URL Parameters
Parameter Name Description
id:long The invoice ID that comes from the search result RoyaltySearch
summarization:alpha (Optional) Can be one of the following values:
  • Invoice
  • Statement
  • Detail
Note: If not specified, Detail is the default value used.
Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
Accept text/xml
Response

See the NonOpRoyalties XML schema for details on the response body for this call. The response will be in either XML or JSON depending on the 'Accept' header specified but the structure and content will be the same.

EnergyLinkClientNonOp

When using RdWebApi an instance of EnergyLinkClientNonOp is always required, e.g.

		
using (var client = new EnergyLinkClientNonOp(
    environment: EnergyLinkEnvironment.Test,
    loginName: "USERID",
    password: "PASSWORD")
)
{
    // Your API calls go here. See examples below.
}
    
	
GET https://api.energylink.com/Api/NonOpAutomation/ValidateInvoice/{id:long}

ValidateInvoice

Identifies all issues preventing this invoice from being vouchered (e.g. missing approval).

Request

	var result = await client.ValidateInvoiceAsync(123);
	
	
Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
POST https://api.energylink.com/Api/NonOpAutomation/CreateAfeXrefs

CreateAfeXrefs

Request

var afeElements = new List<AfeElement>
{
    new AfeElement { 
		AfeId = 123, 
		PrtAfeNumber = "AFE123",								// Optional
		PrtCostCenterCode = "AFECC123",							// Optional
		PrtAfeActionCode = Enums.AfeActionCodes.DisputeSold		// Optional
	}
};
var result = await client.CreateAfeXrefsAsync(afeElements);
	
	
Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
JSON Array Body e.g.

[{
	"AfeId": 123,
	"PrtAfeNumber": "AFE123",				// Optional
	"PrtCostCenterCode": "CC123",			// Optional
	"PrtAfeActionCode": "DisputeSold"		// Optional
}]
    
    
POST https://api.energylink.com/Api/NonOpAutomation/CreateCcXrefs

CreateCcXrefs

Request

var ccElements = new List<CostCenterElement>
{
    new CostCenterElement { 
		CcId = 123, 
		PrtAfeNumber = "AFE123",										// Optional
		PrtCostCenterCode = "CC123",									// Optional
		PrtCostCenterActionCode = Enums.CcActionCodes.DisputeSold		// Optional
	}
};
var result = await client.CreateCcXrefsAsync(ccElements);
	
	
Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
JSON Array Body e.g.

[{
	"CcId": 123,
	"PrtCostCenterCode": "CC001",				// Optional
	"PrtAfeNumber": "AFE001",					// Optional
	"PrtCostCenterActionCode": "DisputeSold"	// Optional
}]

POST https://api.energylink.com/Api/NonOpAutomation/UpdateAfeXrefs

UpdateAfeXrefs

Request

var afeElements = new List<AfeElement>
{
    new AfeElement { 
		AfeId = 123, 
		AfeXrefId = 456,
		PrtAfeNumber = "AFE123",								// Optional
		PrtCostCenterCode = "AFECC123",							// Optional
		PrtAfeActionCode = Enums.AfeActionCodes.DisputeSold		// Optional
	}
};
var result = await client.UpdateAfeXrefsAsync(afeElements);
	
	
Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
JSON Array Body e.g.

[{
	"AfeId": 123,
	"AfeXrefId": 456,
	"PrtAfeNumber": "AFE123",				// Optional
	"PrtCostCenterCode": "CC123",			// Optional
	"PrtAfeActionCode": "DisputeSold"		// Optional
}]
    
    
POST https://api.energylink.com/Api/NonOpAutomation/UpdateCcXrefs

UpdateCcXrefs

Request

var ccElements = new List<CostCenterElement>
{
    new CostCenterElement { 
		CcId = 123, 
		CcXrefId = 456,
		PrtAfeNumber = "AFE123",										// Optional
		PrtCostCenterCode = "CC123",									// Optional
		PrtCostCenterActionCode = Enums.CcActionCodes.DisputeSold		// Optional
	}
};
var result = await client.UpdateCcXrefsAsync(ccElements);
	
	
Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
JSON Array Body e.g.

[{
	"CcId": 123,
	"CcXrefId": 456,
	"PrtCostCenterCode": "CC001",				// Optional
	"PrtAfeNumber": "AFE001",					// Optional
	"PrtCostCenterActionCode": "DisputeSold"	// Optional
}]

POST https://api.energylink.com/Api/NonOpAutomation/UpdateStatementDisputes/{id:long}

UpdateStatementDisputes

Request

var statementDispute = new StatementDisputeElement
{
	DisputeReason = Enums.DisputeReasonCode.Sold,
	DisputeComment = "Our records indicate that we have sold this property",
	DisputeEntireStatement = true
};
var result = await EnergyLinkClient.UpdateStatementDisputesAsync(12345, statementDispute);
	
	
Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
JSON Body Dispute Entire Statement e.g.

{
	"DisputeReason": "Sold",
	"DisputeComment": "Our records indicate that we have sold this property",
    "DisputeEntireStatement": true
}
    
	
JSON Body Dispute Statement Details e.g.

// To undo a specific detail dispute, enter the full billed amount into AcceptedPartnerAmount
{
	"DisputeReason": "Self_Insured",
	"DisputeComment": "Our records indicate that this property is self-insured",
    "DetailDisputes": [
		{
			"DetailId": 123,
			"AcceptedPartnerAmount": null
		}
	]
}
    
	
POST https://api.energylink.com/Api/NonOpAutomation/ApproveInvoices

ApproveInvoices

Request

var invoiceIds = new List<long> { 12345, 12346 };
var result = await client.ApproveInvoicesAsync(invoiceIds);
    
	
Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
JSON Array Body e.g.

[
	12345,
	12346
]
    
    
POST https://api.energylink.com/Api/NonOpAutomation/ApproveStatement/{id:long}

ApproveStatement

Request

//optional. you can pass a null as the 2nd parameter to approve the entire statement
var statementApproval = new StatementApprovalElement
{
	ExceptDetailIds = new List<long> { 12346 }
};
var result = await client.ApproveStatementAsync(12345, statementApproval);
    
	
Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
JSON Array Body e.g.

{
	"ExceptDetailIds": [
		12345,
		12346
	]
}
    
    
POST https://api.energylink.com/Api/NonOpAutomation/AssignVoucherReferences

AssignVoucherReferences

Request

var items = new List<AssignVoucherReferencesRequestItem>
{
	new AssignVoucherReferencesRequestItem
	{
		InvoiceId = 123,
		VoucherReference = "VCH123",
		VoucherAccountingMonth = new DateTime(2018, 11, 30, 12, 0, 0, DateTimeKind.Local)
	}
};
var result = await client.AssignVoucherReferencesAsync(items);
var resultContent = await result.Content.ReadAsStringAsync();
//resultContent is a list of InvoiceIds and their new VoucherIds
var responseItems = JsonConvert.DeserializeObject<List<EnergyLinkClientNonOp.AssignVoucherReferencesResponseItem>>(content);
	
    
Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
JSON Array Body e.g.

[
  {
	"InvoiceId": 123,
	"VoucherReference": "VCH123",
    "VoucherAccountingMonth": "2011-07-14T12:00:00-0600"
  },
  {
	"InvoiceId": 234,
	"VoucherReference": "VCH234",
	"VoucherAccountingMonth": "2011-07-14T12:00:00-0600"
  }
]
    
    
Response

[
  {
    "InvoiceId": 123,
    "VoucherId": 986
  },
  {
    "InvoiceId": 234,
    "VoucherId": 987
  }
]
	
	
GET https://api.energylink.com/Api/NonOpAutomation/ValidateVoucher/{id:long}

ValidateVoucher

Identifies all issues preventing this voucher from being created (e.g. missing approval).

Request

	var result = await client.ValidateVoucherAsync(123);
	
	
Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
POST https://api.energylink.com/Api/NonOpAutomation/QueueVoucher/{id:long}

QueueVoucher

Attempt to queue the voucher for creation. When finished the file will be available via the methods outlined under Non-Operated File Downloads.

Request

	var result = await client.QueueVoucherAsync(123);
	
	
Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==

Non-Operated Automation Samples


The following sample searches for invoices received within the past 2 days and attempts to:

  • Complete missing coding
  • Dispute invalid charges
  • Validate whether the invoice is ready for download
  • Approve the invoice
  • Assign a voucher reference and queue the voucher for creation
Simply include RdWebApi in your project, create a class inheriting from InvoiceProcessingBot, then run ProcessInvoices(). See Non-Operated File Downloads for options to automatically download the voucher file and PDF backup.

		
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
using RedDog.RdWebApi.NonOp;

namespace RedDog.RdWebApi.Samples
{
	public abstract class InvoiceProcessingBot
	{
		public abstract EnergyLinkEnvironment EnergyLinkEnvironment { get; }
		public abstract string EnergyLinkLoginName { get; }
		public abstract string EnergyLinkPassword { get; }
		protected virtual EnergyLinkClientNonOp EnergyLinkClient { get; set; }
		public abstract bool DoApprovals { get; }
		public abstract bool AssignVoucherRefIfApproved { get; }
		public abstract bool QueueVoucherIfVoucherRefAssigned { get; }
		
		public virtual async Task ProcessInvoices()
		{
			using (EnergyLinkClient ?? (EnergyLinkClient = new EnergyLinkClientNonOp(EnergyLinkEnvironment, EnergyLinkLoginName, EnergyLinkPassword)))
			{
				Log("Getting Invoices", 1);
				var invoiceSearch = await GetInvoiceSearch();
				foreach (var invoice in invoiceSearch.Invoices)
				{
					var invoiceWithDetail = (await EnergyLinkClient.GetInvoiceAsync(invoice.InvoiceId, Enums.InvoiceSummarization.Detail)).Invoices[0];
					await ProcessInvoice(invoiceWithDetail);
					await ApproveAndVoucherInvoice(invoiceWithDetail);
				}
			}
		}

		protected virtual async Task<NonOpInvoicesElement> GetInvoiceSearch()
		{
			using (var client = new EnergyLinkClientNonOp(EnergyLinkEnvironment, EnergyLinkLoginName, EnergyLinkPassword))
			{
				//get a list of JIBs received in the past 2 days still in Received or Viewed status
				return await client.GetInvoiceSearchAsync(
					Enums.DateSearchType.RECEIVED_DATE, 
					DateTime.Today.AddDays(-2), 
					invoiceType: new []{ Enums.InvoiceTypes.JIB }, 
					status: new []{ Enums.InvoiceStatusCodes.Received, Enums.InvoiceStatusCodes.Viewed }
				);
			}
		}

		#region Process Invoice
		protected virtual async Task ProcessInvoice(InvoiceElement invoice)
		{
			Log($"Processing {invoice.Operator.OrgName} Invoice {invoice.InvoiceNumber}", 1);

			var newAfeXrefs = new Dictionary<long, AfeElement>();
			var newCcXrefs = new Dictionary<long, CostCenterElement>();
			
			foreach (var statement in invoice.Statements)
			{
				await ProcessStatement(statement);

				if (!statement.AcceptedPartnerAmount.HasValue && !statement.AcceptedGstAmount.HasValue && !statement.AcceptedCashCallAmount.HasValue)
				{
					//Mapping is not required for fully disputed statements
					continue;
				}

				if (statement.Afe != null && !statement.Afe.AfeXrefId.HasValue)
				{
					if (TryMapAfe(statement.Afe) && !string.IsNullOrWhiteSpace(statement.Afe.PrtAfeNumber))
					{
						newAfeXrefs.Add(statement.Afe.AfeId, statement.Afe);
					}	
				}

				if (statement.CostCenter != null && !statement.CostCenter.CcXrefId.HasValue)
				{
					if (TryMapCostCenter(statement.CostCenter) && !string.IsNullOrWhiteSpace(statement.CostCenter.PrtCostCenterCode))
					{
						newCcXrefs.Add(statement.CostCenter.CcId, statement.CostCenter);
					}	
				}
			}

			if (newAfeXrefs.Count > 0)
			{
				foreach (var newAfeXref in newAfeXrefs.Values)
				{
					Log($"OpAfeNumber '{newAfeXref.OpAfeNumber}' will be mapped to '{newAfeXref.PrtAfeNumber}'");
				}
				var result = await EnergyLinkClient.CreateAfeXrefsAsync(newAfeXrefs.Values.ToList());
				if (!result.IsSuccessStatusCode) throw new InvalidOperationException($"Error mapping {newAfeXrefs.Count} AFEs: {result.ReasonPhrase}");
				Log($"Mapped {newAfeXrefs.Count} AFEs");
			}
			
			if (newCcXrefs.Count > 0)
			{
				foreach (var newCcXref in newCcXrefs.Values)
				{
					Log($"OpCostCenterCode '{newCcXref.OpCostCenterCode}' will be mapped to '{newCcXref.PrtCostCenterCode}'");
				}
				var result = await EnergyLinkClient.CreateCcXrefsAsync(newCcXrefs.Values.ToList());
				if (!result.IsSuccessStatusCode) throw new InvalidOperationException($"Error mapping {newCcXrefs.Count} CostCenters: {result.ReasonPhrase}");
				Log($"Mapped {newCcXrefs.Count} CostCenters");
			}
		}

		protected virtual bool TryMapAfe(AfeElement afe)
		{
			//Implement logic here to set the PrtAfeNumber (and optionally the PrtCostCenterCode, PrtAfeActionCode)
			return false;
		}

		protected virtual bool TryMapCostCenter(CostCenterElement cc)
		{
			//Implement logic here to set the PrtCostCenterCode (and optionally the PrtAfeNumber, PrtCostCenterActionCode)
			return false;
		}

		protected virtual async Task ProcessStatement(StatementElement statement)
		{
			//Hint: Clients using Desk Audit can easily set their 'Sold' and 'No Interest'
			//property flags online based on data from the land system
			switch (statement.CostCenter?.PrtCostCenterActionCode)
			{
				case Enums.CcActionCodes.DisputeNoInterest:
					if (await DisputeNoInterest(statement)) return;
					break;
				case Enums.CcActionCodes.DisputeSold:
					if (await DisputeSold(statement)) return;
					break;
			}

			//If we are not rejecting the property outright then continue onto possible detail-level disputes.
			//Note: There can only be one Dispute Reason and Dispute Comment
			//		per statement so pick the "most important" one that applies.
			//		In this case we assume that DOI > Overhead > Self-Insured
			var statementDispute = new StatementDisputeElement
			{
				DetailDisputes = new List<DetailDisputeElement>()
			};
			
			foreach (var detail in statement.Details)
			{
				if (DisputeDoi(statement, detail, out var correctDoi))
				{
					if (!statementDispute.DisputeReason.HasValue)
					{
						statementDispute.DisputeReason = Enums.DisputeReasonCode.Wrong_DOI;
						statementDispute.DisputeComment = $"Our records indicate that this DOI should be {correctDoi}.";
					}
					statementDispute.DetailDisputes.Add(new DetailDisputeElement { DetailId = 123, AcceptedPartnerAmount = null });
				}
				else if (IsOverhead(detail) && DisputeOverhead(statement, detail, out var correctOverhead))
				{
					if (!statementDispute.DisputeReason.HasValue)
					{
						statementDispute.DisputeReason = Enums.DisputeReasonCode.Overhead_Fee;
						statementDispute.DisputeComment = $"Our records indicate that the overhead rate should be {correctOverhead}.";
					}
					statementDispute.DetailDisputes.Add(new DetailDisputeElement { DetailId = 123, AcceptedPartnerAmount = null });
				}
				else if (IsInsurance(detail) && DisputeInsurance(statement))
				{
					if (!statementDispute.DisputeReason.HasValue)
					{
						statementDispute.DisputeReason = Enums.DisputeReasonCode.Self_Insured;
						statementDispute.DisputeComment = "Our records indicate that this property is self-insured.";
					}
					statementDispute.DetailDisputes.Add(new DetailDisputeElement { DetailId = 123, AcceptedPartnerAmount = null });
				}
			}

			if (statementDispute.DisputeReason.HasValue && statementDispute.DetailDisputes.Count > 0)
			{
				var result = await EnergyLinkClient.UpdateStatementDisputesAsync(statement.StatementId, statementDispute);
				if (!result.IsSuccessStatusCode) 
					throw new InvalidOperationException($"Error updating disputes on StatementId {statement.StatementId}: {result.ReasonPhrase}");
			}

			await Task.FromResult(0);
		}

		protected virtual async Task<bool> DisputeNoInterest(StatementElement statement)
		{
			var statementDispute = new StatementDisputeElement
			{
				DisputeReason = Enums.DisputeReasonCode.NoInterest,
				DisputeComment = "Our records indicate that we have no interest in this property",
				DisputeEntireStatement = true
			};
			var result = await EnergyLinkClient.UpdateStatementDisputesAsync(statement.StatementId, statementDispute);
			if (!result.IsSuccessStatusCode) 
				throw new InvalidOperationException($"Error updating disputes on StatementId {statement.StatementId}: {result.ReasonPhrase}");
			return true;
		}

		protected virtual async Task<bool> DisputeSold(StatementElement statement)
		{
			var statementDispute = new StatementDisputeElement
			{
				DisputeReason = Enums.DisputeReasonCode.Sold,
				DisputeComment = "Our records indicate that we have sold this property",
				DisputeEntireStatement = true
			};
			var result = await EnergyLinkClient.UpdateStatementDisputesAsync(statement.StatementId, statementDispute);
			if (!result.IsSuccessStatusCode) 
				throw new InvalidOperationException($"Error updating disputes on StatementId {statement.StatementId}: {result.ReasonPhrase}");
			return true;
		}

		protected virtual bool DisputeDoi(StatementElement statement, DetailElement detail, out decimal? correctDoi)
		{
			//Implement logic here to decide if this DOI should be disputed (e.g. if it does not match your land system)
			//Hint: Clients using Desk Audit can easily get this info from the EnergyLink API calls included in this package
			correctDoi = 50;
			return false;
		}

		protected virtual bool IsOverhead(DetailElement detail)
		{
			//Hint: Compare detail.PrtMajorAccountCode and detail.PrtMinorAccountCode to your GL account(s) for overhead
			return detail.EnergyLinkMinorAccountDesc.IndexOf("OVERHEAD", StringComparison.InvariantCultureIgnoreCase) >= 0;
		} 
		
		protected virtual bool DisputeOverhead(StatementElement statement, DetailElement detail, out decimal? correctOverhead)
		{
			//Implement logic here to decide if this overhead line should be disputed (e.g. if it does not match your land system)
			//Hint: Clients using Desk Audit can easily get this info from the EnergyLink API calls included in this package
			correctOverhead = 150;
			return false;
		}

		protected virtual bool IsInsurance(DetailElement detail)
		{
			//Hint: Compare detail.PrtMajorAccountCode and detail.PrtMinorAccountCode to your GL account(s) for insurance
			return detail.EnergyLinkMinorAccountDesc.IndexOf("INSURANCE", StringComparison.InvariantCultureIgnoreCase) >= 0;
		} 
		
		protected virtual bool DisputeInsurance(StatementElement statement)
		{
			//Implement logic here to decide if insurance should be disputed on this property (e.g. if it is self-insured)
			return false;
		}
		#endregion

		#region Approve and Voucher
		protected virtual async Task ApproveAndVoucherInvoice(InvoiceElement invoice)
		{
			var validateResult = await EnergyLinkClient.ValidateInvoiceAsync(invoice.InvoiceId);
			if (validateResult.Validations.Any(x => x.Key != Enums.Validations.APPROVAL_REQD))
			{
				Log($"Validation Summary:\r\n{validateResult.ValidationSummary}");
				Log($"Invoice {invoice.InvoiceNumber} has outstanding validations - cannot continue.");
				return;
			}

			if (!invoice.IsApproved)
			{
				if (!DoApprovals)
				{
					Log("Not configured to approve invoices - done with this invoice.");
					return;
				}

				if (ApproveInvoice(invoice))
				{
					var result = await EnergyLinkClient.ApproveInvoicesAsync(new List<long> {invoice.InvoiceId});
					if (!result.IsSuccessStatusCode) throw new InvalidOperationException($"Error approving InvoiceId {invoice.InvoiceId}: {result.ReasonPhrase}");
					Log($"Invoice {invoice.InvoiceNumber} approved.");
				}
				else
				{
					Log("Did not meet criteria for approval - done with this invoice.");
					return;
				}
			}

			if (validateResult.OtherInvoicesWithValidations?.Count > 0)
			{
				Log($"Invoice {invoice.InvoiceNumber} is ready for download but InvoiceIds {string.Join(",", validateResult.OtherInvoicesWithValidations.Select(x => x.ToString()))} share the voucher and still have validations.");
				return;
			}

			if (!invoice.ActiveVoucherId.HasValue)
			{
				if (!AssignVoucherRefIfApproved)
				{
					Log("Not configured to assign voucher references - done with this invoice.");
					return;
				}

				var voucher = AssignVoucher(invoice);
				var result = await EnergyLinkClient.AssignVoucherReferencesAsync(new List<EnergyLinkClientNonOp.AssignVoucherReferencesRequestItem> {voucher});
				if (!result.IsSuccessStatusCode) throw new InvalidOperationException($"Error assigning voucher reference to InvoiceId {invoice.InvoiceId}: {result.ReasonPhrase}");
				var content = await result.Content.ReadAsStringAsync();
				var response = JsonConvert.DeserializeObject<List<EnergyLinkClientNonOp.AssignVoucherReferencesResponseItem>>(content);
				if (response?.Count != 1 || !response[0].VoucherId.HasValue) throw new InvalidOperationException($"Error assigning voucher reference to InvoiceId {invoice.InvoiceId}: No VoucherId was returned.");
				Log($"Voucher Reference {invoice.InvoiceNumber} assigned.");

				if (!QueueVoucherIfVoucherRefAssigned)
				{
					Log("Not configured to queue vouchers - done with this invoice.");
					return;
				}
				var voucherId = response[0].VoucherId.Value;
				var queueResult = await EnergyLinkClient.QueueVoucherAsync(voucherId);
				if (!queueResult.IsSuccessStatusCode) throw new InvalidOperationException($"Error queueing VoucherId {voucherId}: {result.ReasonPhrase}");
				Log($"Voucher {voucher.VoucherReference} queued for creation.");
			}
		}

		protected virtual bool ApproveInvoice(InvoiceElement invoice)
		{
			//Implement logic here to decide whether to approve an invoice for download if the validations are satisfied, e.g.:
			var invoiceAmount = (invoice.OriginalPartnerAmount ?? 0) + (invoice.OriginalGstAmount ?? 0) + (invoice.OriginalCashCallAmount ?? 0);
			return Math.Abs(invoiceAmount) <= 100;
		}

		protected virtual EnergyLinkClientNonOp.AssignVoucherReferencesRequestItem AssignVoucher(InvoiceElement invoice)
		{
			//Implement logic here to assign the voucher reference and accounting month, e.g.:
			var voucher = new EnergyLinkClientNonOp.AssignVoucherReferencesRequestItem
			{
				InvoiceId = invoice.InvoiceId,
				VoucherReference = $"{invoice.InvoiceNumber}.{invoice.InvoiceId}",
				VoucherAccountingMonth = DateTime.Today
			};
			//Note: appending InvoiceId will ensure uniqueness if using one invoice per voucher
			return voucher;
		}
		#endregion

		protected virtual void Log(string message, int tabs = 2)
		{
			Trace.WriteLine($"{DateTime.Now:yyyy-MM-dd hh:mm:ss}{string.Concat(Enumerable.Repeat("\t", tabs))}{message}");
		}
	}
}
			
		

Inquiry Automation File Downloads

The following methods may be used to move 'Created' inquiries (including ACH and COA) and their related files from EnergyLink to the client's environment. Upon download the status will change to 'Downloaded' and the inquiry will no longer be returned in the 'Inquiries for Download' queries. An inquiry can be set from 'Downloaded' back to 'Created' by manually re-creating it via the website.

By default users must create inquiries using the EnergyLink website.

It is possible to have custom fields added to the as response as needed. This will have to be set up with EnergyLink support

Web API Polling Recommended for Vendors

A User ID and Password will be provided by EnergyLink Support. This is a service account only and cannot log into the website using a web browser. A separate user account can be provided if you need to log into the site for testing purposes. Each interface is available in both the Production and Demo environments but a separate service account is used in each.

When using REST, the Authentication header is always required as follows:
  • Concatenate the User ID and Password using a colon, e.g. "USERID:PASSWORD"
  • Base-64 encode this string, e.g. "VVNFUklEOlBBU1NXT1JE==". This can be done here.
  • Use Basic Authentication in the "Authorization" header, e.g. "Basic VVNFUklEOlBBU1NXT1JE=="
  • Note: If testing in testing environment use https://api-demo.energylink.com in the URL
GET https://api.energylink.com/Api/Inquiry/GetAchExport?isTesting=False

GetAchExport

Returns the unprocessed owner inquiries for changes to their electronic payment information. When isTesting=False, receiving these will mark the inquiry as processed on EnergyLink

Request
	
using (var client = new EnergyLinkClientInquiry(
	environment: EnergyLinkEnvironment.Test,
	loginName: "USERID",
	password: "PASSWORD")
)
{
	var result = await client.AchExport;
}
		
	
Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
Response

The response will be in JSON. There are possibly more fields depending on your company's configuration. Your configuration can be updated by EnergyLink Support. The VoidCheckFile is a binary file that has been encoded to a base 64 string. Decode it from a base 64 string into an array of bytes and that can be saved as a file.

	
{
	"OpInquiryPlusElectronicPaymentJson": {
		"Inquiries": [
			{
				"InquiryId": 10000,
				"OwnerId": "99999",
				"OwnerName": "TEST OIL AND GAS",
				"RequestType": "New",
				"RelationshipToOwner": "Self",
				"TaxId": "1234",
				"AccountType": "Checking",
				"ABARouting": "999999999",
				"BankName": "BANK OF BANK",
				"BankAddress": "123 FAKE STREET",
				"BankCity": "BANK CITY",
				"BankState": "TX",
				"BankZip": "00000-0000",
				"NameOnBankAccount": "Test Person",
				"AccountNumber": "12345678",
				"EmailForPaymentNotification": "my.email@email.com",
				"VoidCheck": {
				"VerifyDocFileName": "10000 EnergyLink Jan 2023 Invoice MTD.123456.DEMO Summary.OPERATOR CORP.pdf",
				"VoidCheckFile": "base_64_pdf"
				},
				"Signature": "Test Person",
				"CreatedTS": "2023-04-01T14:31:02.969553-06:00",
				"CreatedUser": "Test Person"
			}
		]
	}
}
	
GET https://api.energylink.com/Api/Inquiry/GetCoaExport?isTesting=False

GetCoaExport

Returns the unprocessed owner inquiries for changes to their address information. When isTesting=False, receiving these will mark the inquiry as processed on EnergyLink

Request
	
using (var client = new EnergyLinkClientInquiry(
	environment: EnergyLinkEnvironment.Test,
	loginName: "USERID",
	password: "PASSWORD")
)
{
	var result = await client.GetCoaExport;
}
		
	
Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
Response

The response will be in JSON'

	
{
	"OpInquiryPlusChangeOfAddressJson": {
		"Inquiries": [
			{
				"InquiryId": 82297,
				"OwnerId": "9999999",
				"OwnerName": "FAKE OWNER LLC",
				"TaxId": "4632",
				"Address1": "123 FAKE STREET",
				"Address2": "",
				"Address3": "",
				"City": "FAKE CITY",
				"State": "TX",
				"Country": "USA",
				"Zip": "00000",
				"Phone": "9999999999",
				"Email": "test.person@email.com",
				"Signature": "Test Person",
				"CreatedTS": "2023-04-01T17:04:15.349934-06:00",
				"CreatedUser": "Test Person"
			}
		]
	}
}
	
POST https://api.energylink.com/Api/Inquiry/InquiryOwnerInfoCsv

InquiryOwnerInfoCsv

Request

var result = await client.UploadInquiryOwnerInfoCsvAsync(
	stream,	//e.g. MemoryStream, FileStream, OracleBlob
	"file.csv.zip"
);
    
	
Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
FileName file.zip

Request body is the file data.

POST https://api.energylink.com/Api/Inquiry/CloseAchs

CloseAchs

Request

var items = new List<AssignVoucherReferencesRequestItem>
{
	new AssignVoucherReferencesRequestItem
	{
		InquiryId = 123,
		CloseInquiryMessage = "Some message",
	}
};
var result = await client.UploadInquiryCloseRequestJsonAsync(items);
var resultContent = await result.Content.ReadAsStringAsync();   //resultContent is a list of InquiryIds and the message
	
    
Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
JSON Array Body e.g.
	
[
    {
     "InquiryId": 123,
     "CloseInquiryMessage": "Some message",
    },
    {
     "InquiryId": 234,
     "CloseInquiryMessage": "Some message2",
    }
]
	
POST https://api.energylink.com/Api/Inquiry/CloseCoas

CloseCoas

Request

var items = new List<AssignVoucherReferencesRequestItem>
{
	new AssignVoucherReferencesRequestItem
	{
		InquiryId = 123,
		CloseInquiryMessage = "Some message",
	}
};
var result = await client.UploadInquiryCloseRequestJsonAsync(items);
var resultContent = await result.Content.ReadAsStringAsync();   //resultContent is a list of InquiryIds and the message
	
    
Request
Request Header Name Sample Value Note
Authorization Basic VVNFUklEOlBBU1NXT1JE==
JSON Array Body e.g.
	
[
    {
     "InquiryId": 123,
     "CloseInquiryMessage": "Some message",
    },
    {
     "InquiryId": 234,
     "CloseInquiryMessage": "Some message2",
    }
]