solvebeam / twinfield
Modern PHP SDK for the Twinfield API
Fund package maintenance!
Requires
- php: >=8.3
- ext-bcmath: *
- ext-dom: *
- ext-libxml: *
- ext-mbstring: *
- ext-simplexml: *
- league/oauth2-client: ^2.9
- php-http/discovery: ^1.20
- psr/cache: ^2.0 || ^3.0
- psr/http-client: ^1.0
- psr/http-factory: ^1.1
- psr/http-message: ^2.0
Requires (Dev)
- monolog/monolog: ^3.10
- nyholm/psr7: ^1.8
- php-http/logger-plugin: ^1.5
- php-http/message: ^1.16
- phpstan/phpstan: ^2.3
- phpunit/phpunit: ^12
- rector/rector: ^2.3
- squizlabs/php_codesniffer: ^3.10
- symfony/cache: ^7.0
- symfony/http-client: ^7.0 || ^8.0
- vlucas/phpdotenv: ^5.6
Suggests
- monolog/monolog: For PSR-3 logging of HTTP requests and responses
- nyholm/psr7: For a lightweight PSR-7 implementation with the HTTP client
- php-http/curl-client: For using pure cURL-based PSR-18 HTTP client
- php-http/logger-plugin: For logging HTTP traffic (requires monolog or another PSR-3 logger)
- php-http/message: For HTTP message formatters (FullHttpMessageFormatter for complete request/response logging)
- symfony/http-client: For using Symfony's lightweight PSR-18 HTTP client instead of Guzzle
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-10 22:15:48 UTC
README
PHP Twinfield SDK
Modern PHP SDK for the Twinfield API, built on PSR standards.
Documentation
·
Examples
Contents
- Installation
- Quick start
- Organisations and companies
- Master data
- Transactions
- Browse data
- Declarations
- Hierarchies
- Matching
- Pay & Collect
- Command & Query web services
- XML web services (ProcessXml)
- REST API
- Errors
- Backward compatibility
- PSR Compliance
- OAuth2 Usage
- OpenID Connect Authentication
- Development and testing
- About
- Links
Installation
The SDK needs PHP 8.3 or newer and a PSR-18 HTTP client with PSR-17 factories.
composer require solvebeam/twinfield
The SDK does not ship an HTTP client. Install one you like, for example Symfony's client with Nyholm's PSR-7 implementation:
composer require symfony/http-client nyholm/psr7
Twinfield::create() finds an installed client and factories automatically through php-http/discovery. See Installation for other clients and optional packages.
Quick start
use SolveBeam\Twinfield\Activity\ActivityInput; use SolveBeam\Twinfield\Finder\FinderType; use SolveBeam\Twinfield\Twinfield; $twinfield = Twinfield::create($accessTokenProvider)->company('1000'); foreach ($twinfield->offices()->list() as $office) { echo $office->code, ' ', $office->name, PHP_EOL; } $debtors = $twinfield->finder(FinderType::Dimensions) ->option('dimtype', 'DEB') ->limit(10) ->get(); foreach ($twinfield->periods()->list(2025) as $details) { echo $details->period, ' ', $details->name ?? '-', PHP_EOL; } $activity = $twinfield->activities()->create( ActivityInput::create('A100', 'Consultancy')->customer('1010')->billable(true), );
See examples/ for small, runnable examples, starting with Authentication.
Organisations and companies
Company and user codes are only unique within a Twinfield organisation: company 1000 can exist in several organisations. Every model the SDK reads therefore carries the organisation of the access token, either as $office->organisation and $user->organisation, or as $customer->company (a CompanyReference with the organisation and the company code). $twinfield->company($customer->company) refuses a company of another organisation. See Organisations and companies for the keys to use in a shadow database.
$customer = $twinfield->customers()->get('1000'); echo $customer->company->organisation->code, ' ', $customer->company->code, ' ', $customer->code, PHP_EOL;
Master data
Master data such as customers, general ledger accounts, activities, cost centers and fixed assets are dimensions, read and written with the XML web services. See the master data guide. Where live Twinfield responses differ from the API documentation, the SDK follows the live responses: see Differences with the documentation.
| Dimension type | Resource | Status |
|---|---|---|
Activities (ACT) |
$twinfield->activities() |
Search, read, create, update, delete, activate. See List activities and Create activity. |
Cost centers (KPL) |
$twinfield->costCenters() |
Search, read, create, update, delete, activate. See List cost centers and Create cost center. |
Customers (DEB) |
$twinfield->customers() |
Search (also by bank account and address), read, create, update, delete, activate; addresses, bank accounts, SEPA direct debit, credit management, posting rules. See Read customer and Create customer. |
Fixed assets, classic module (AST) |
$twinfield->classicFixedAssets() |
Search (also by group), read, create, update, delete, activate; asset method, depreciation settings, free texts, transaction lines and locked settings. See Read fixed asset and Create fixed asset. |
General ledger accounts (BAS, PNL) |
$twinfield->balanceSheetAccounts(), $twinfield->profitAndLossAccounts() |
Search (also hidden), read, create, update, delete, activate; sub-analysis, child validations, VAT code, groups. See Read general ledger account and Create general ledger account. |
Projects (PRJ) |
$twinfield->projects() |
Search, read, create, update, delete, activate; valid dates, customer, rate, authoriser, billable, quantities and a VAT code for all activities. See Read project and Create project. |
Suppliers (CRD) |
$twinfield->suppliers() |
Search (also by bank account and address), read, create, update, delete, activate; credit transfer or cheques, external supplier number, remittance advice, addresses, bank accounts, posting rules. See Read supplier and Create supplier. |
| Other master data | Resource | Status |
|---|---|---|
| Articles | $twinfield->articles() |
Search, read, create, update, delete, activate; the articles (items) of Classic sales invoices, with their sub-articles: prices, revenue account and cost center. See Read article and Create article. |
| Asset methods, classic module | $twinfield->classicAssetMethods() |
Search, read, create, update, delete, activate. See Read asset method and Create asset method. |
| Bank books | $twinfield->bankBooks() |
Search, read, create, update (Command & Query; Twinfield cannot delete them). See Read bank book and Update bank book. |
| Cash books | $twinfield->cashBooks() |
Search, read, create, update (Command & Query; Twinfield cannot delete them). See Read cash book and Update cash book. |
| Currencies | $twinfield->currencies() |
Search, read, create, update, delete, with exchange rates (rateOn($date)). See Read currency and Update currency rates. |
| Rates | $twinfield->rates() |
Search (also by type: hours or quantities), read, create, update, delete, activate; prices per date range with priceOn($date) and newPriceFrom(). See Read rate and Update rate price. |
| VAT codes | $twinfield->vatCodes() |
Search (also by type: sales or purchases), read, create, update, delete, activate; percentages over time with percentageOn($date), vatOn($amount, $date) and newPercentageFrom(), accounts and VAT groups. See Read VAT code and Update VAT percentage. |
| Dimension groups | $twinfield->dimensionGroups() |
Search (also by dimension type), read, create, update, delete; dimensions of any type. See Read dimension group and Update dimension group. |
| Dimension types | $twinfield->dimensionTypes() |
Search (also by financial level and by dimension code), read, update name, code mask and address labels; $type->mask->matches($code). See Read dimension type and Update dimension type. |
| Budgets | $twinfield->budgets() |
Search, budget vs. actual per account, cost center or project (Command & Query; read-only). See Budget vs. actual. |
| Users | $twinfield->users() |
List and search, read (also the user of the access token with current()), create, update, delete, activate; role, login status, company access with grantAccessTo() and revokeAccessTo(), invitations. See Read user and Current user. |
| Offices | $twinfield->offices() |
List and search (also without tax group) with company ID, read all settings: currencies, VAT, system dimensions, intercompany and locked settings (read-only). company($office) takes an office. See Read office and Search offices. |
Transactions
Transactions are read and written with the XML web services. You add lines with receive() and pay(); the SDK chooses debit or credit and calculates the total line and the closing balance. Amounts are exact numeric strings (Amount), never floats. See the transactions guide.
use SolveBeam\Twinfield\BankTransaction\BankTransactionInput; $transaction = $twinfield->bankTransactions()->create( BankTransactionInput::create('BNK', '1010', new DateTimeImmutable('2026-11-03')) ->statement(245, startValue: '1000.00') ->receive('1300', '121.00', customer: '1000', invoice: '2026-0042') ->pay('4500', '10.00', vatCode: 'IH', vatValue: '2.10', description: 'Bank costs'), );
| Transactions | Resource | Status |
|---|---|---|
| Bank | $twinfield->bankTransactions() |
Read, create, update, save many, post, delete, match with other transactions. See Create bank transaction and Import bank statement. |
| Cash | $twinfield->cashTransactions() |
Read, create, update, save many, post, delete. See Daily cash register. |
| Sales | $twinfield->salesTransactions() |
Read, create, update, save many, post, delete, change pay status, open invoices (Finder IVT). See Create sales transaction and Credit note. |
| Sales invoices | $twinfield->salesInvoices() |
Classic invoicing module: read, create, update, calculate without saving, make final and read the booked sales transaction, invoice types (Finder INV). See Create sales invoice and Read sales invoice. |
| Journal | $twinfield->journalTransactions() |
Read, create, update, save many, post, delete; memorial, wages, opening balance and year end. See Create journal transaction and Opening balance. |
| Purchase | $twinfield->purchaseTransactions() |
Read, create, update, save many, post, delete, change pay status, open invoices (Finder IVT). See Create purchase transaction and Credit note. |
| Electronic bank statements | $twinfield->electronicBankStatements() |
Import bank statements and let Twinfield allocate the lines: create, create many. Read statements with their allocations and matchings (BankStatementService): list by statement date or creation date. See Import electronic bank statement and List electronic bank statements. |
| Reversal | $twinfield->reversalTransactions() |
Reversals of journal transactions: read, post final, post provisional, undo. See Read reversal and Reverse journal transaction. |
| Spread | $twinfield->spreadTransactions() |
Spreads of sales and purchase transactions over a number of periods: read, post final, post provisional (both also per detail line), undo. See Read spread and Spread purchase transaction. |
| Time entry | $twinfield->timeEntries() |
Time and quantities of a user on a project and an activity: save, save many. See Create time entry. |
Browse data
Financial data such as general ledger transaction lines, open items and balances comes from browse data: a browse code with the columns you want and selection criteria. The query is immutable and the rows are read strictly, with amounts as Amount. See the browse data guide.
use SolveBeam\Twinfield\BrowseData\BrowseCode; $result = $twinfield->browse(BrowseCode::GeneralLedgerDetailsV3) ->columns('fin.trs.head.code', 'fin.trs.head.number', 'fin.trs.line.dim1', 'fin.trs.line.valuesigned') ->between('fin.trs.head.yearperiod', '2026/00', '2026/55') ->equal('fin.trs.head.status', 'normal') ->get(); foreach ($result as $row) { echo $row->string('fin.trs.head.code'), ' ', $row->int('fin.trs.head.number'), ' ', $row->amount('fin.trs.line.valuesigned'), PHP_EOL; }
The definitions of all browse codes are in browse-definitions/. See Read browse definition and, for keeping a copy of the transactions, Initial load, Incremental load and Validate balances.
Declarations
VAT, ICT and tax group returns come from the declarations service: list them per company or for the whole organisation, download them as XBRL or XML, get the payment reference and mark them as sent, approved or rejected. See the declarations guide.
use SolveBeam\Twinfield\Declaration\DeclarationType; $declarations = $twinfield->company('1000')->declarations(); foreach ($declarations->list(year: 2026) as $return) { if ($return->type === DeclarationType::VatTurnover) { file_put_contents($return->id . '.xbrl', $declarations->vatReturnXbrl($return->id)); } }
See List declarations and Download VAT return.
Hierarchies
Hierarchies, reporting structures (rapportagestructuren) such as RGS, come from the hierarchies service: read them with their nodes and the general ledger accounts of a company, draw them with RecursiveTreeIterator, save them, bind and unbind accounts, hide and delete them. See the hierarchies guide.
$hierarchy = $twinfield->company('1000')->hierarchies()->get('TWFRGS32'); foreach (new RecursiveTreeIterator($hierarchy) as $line) { echo $line, PHP_EOL; }
See Print hierarchy tree and the other hierarchy examples.
Matching
Matching (afletteren) links existing transaction lines with each other, e.g. a sales invoice and its payment: fully, partly, or with a discount, write-off or exchange rate difference. Undo match undoes a match set by its match number, which is in browse data. See the matching guide.
use SolveBeam\Twinfield\DimensionType\DimensionTypeCode; use SolveBeam\Twinfield\Matching\MatchInput; use SolveBeam\Twinfield\Matching\WriteOff; $matching = $twinfield->company('1000')->matching(); $matching->match( MatchInput::customers(new DateTimeImmutable('2026-11-10')) ->line('KAS', 202600015, 2) ->line('VRK', 202600071, 1, writeOff: WriteOff::discount('2.00')), ); $matching->undo(DimensionTypeCode::AccountsReceivable, '1000', 12);
See Match transactions, List match sets and Undo match.
Pay & Collect
Pay & Collect creates payment runs (paying suppliers) and direct debit runs (collecting from customers): select the open invoices, change the run, authorise it, post it to the in-transfer ledger and create the SEPA file(s). Every step checks the version of the run. See the Pay & Collect guide.
use SolveBeam\Twinfield\PayAndCollect\PayRunInput; use SolveBeam\Twinfield\PayAndCollect\PayRunModification; $payAndCollect = $twinfield->company('1000')->payAndCollect(); $run = $payAndCollect->create( PayRunInput::create('SEPANLCT', new DateTimeImmutable('2026-11-12'), '2026/11') ->maxDueDate(new DateTimeImmutable('2026-11-30')), )->run; $version = $payAndCollect->modify(PayRunModification::of($run)->excludeSummary(2)); $version = $payAndCollect->authorise($run, $version); $version = $payAndCollect->post($run, $version); $files = $payAndCollect->createFiles($run, $version); $payAndCollect->finish($run, $files->version);
See List pay runs, Create pay run and Pay run lifecycle.
Command & Query web services
Twinfield has three kinds of web services: classic SOAP services such as the Finder, XML web services (ProcessXml) and Command & Query web services. The last kind uses queries to read data and commands to change it. These services always work on one company, so call company() first.
| Service | Resource | Status |
|---|---|---|
| PeriodService | $twinfield->periods() |
Queries years(), list(), get()/find() for one Period (e.g. of a transaction); all commands. See Periods, List periods, Period of transaction and Rename period. |
| BudgetService | $twinfield->budgets() |
All queries: totals()->byProfitAndLoss(), byCostCenter(), byProject(). See Budget vs. actual. |
| BankBookService | $twinfield->bankBooks() |
All queries and commands. See Read bank book. |
| CashBookService | $twinfield->cashBooks() |
All queries and commands. See Read cash book. |
| DeletedTransactionsService | $twinfield->deletedTransactions() |
The only query: list(), filtered by day book and deletion date. See List deleted transactions. |
| BankStatementService | $twinfield->electronicBankStatements() |
Both queries: list() by statement date and listByCreationDate(). See List electronic bank statements. |
| TransactionBlockedValueService | $twinfield->transactionBlockedValues() |
Both commands: register() and unregister() a blocked value on a transaction line. See Register blocked value. |
Services without a resource can be called directly. Give the fields in the order of the service's WSDL (usually alphabetical):
use SolveBeam\Twinfield\CommandQuery\Command; use SolveBeam\Twinfield\CommandQuery\Query; $result = $twinfield->query('PeriodService', new Query('GetPeriods', ['Year' => 2025]), 'GetPeriodsResult'); foreach ($result->list('Periods', 'Period') as $period) { echo $period->int('Number'), ' ', $period->string('Name'), PHP_EOL; } $twinfield->process('PeriodService', new Command('ClosePeriod', ['PeriodNumber' => 1, 'Year' => 2025]));
The readers of the result (string(), int(), bool(), dateTime(), list(), ints()) throw an UnexpectedResponseException when a value is missing or invalid.
XML web services (ProcessXml)
processXml() sends a request to the XML web services (ProcessXmlString on processxml.asmx) and returns the response as DOM document:
$result = $twinfield->processXml('<read><type>dimensions</type><office>1000</office><dimtype>DEB</dimtype><code>1000</code></read>'); echo $result->xpath()->evaluate('string(/dimension/name)'), PHP_EOL;
Comments and whitespace between elements are removed before sending. A response with result="0" throws a ProcessXmlException with every error and warning and the path of its element. Listing companies works without company: $twinfield->withoutCompany()->processXml('<list><type>offices</type></list>').
Not supported, because Twinfield deprecated them or they are only for .NET: ProcessXmlCompressed, ProcessXmlDocument and the <general> root element for bulk requests. See the ProcessXml guide and the examples List companies and Read dimension.
REST API
Twinfield also has a REST API with JSON. The SDK supports all endpoints of its public definition; the definitions are in openapi/, updated daily like the WSDL files. Every REST operation needs its own scope (readFixedAssets, readVat, writeArticles, …, see the Scope enum), and the REST API identifies the company by GUID: $twinfield->companyId() looks it up once. See the REST API guide.
| Swagger tag | Resource | Status |
|---|---|---|
| Fixed Assets | $twinfield->fixedAssets() |
The new fixed assets module: search, read with depreciation per regime, history, classes, groups, reconciliation report. Read-only. The classic module is classicFixedAssets(). See Fixed assets, List fixed assets and Read fixed asset. |
| Sales Articles | $twinfield->salesArticles() |
Search, read (get(), find()), create, update, save(), delete, cultures, import and export (CSV, XLSX). A separate list: the articles of Classic sales invoices are articles(). See List sales articles and Create sales article. |
| Budgets | $twinfield->budgets() |
Next to search and totals: budgets with GUIDs, create, update, delete, annual balances, periods, lines, copy, spread, import and export. See List budget lines and Create budget. |
| Budgeting Spreading Templates | $twinfield->spreadingTemplates() |
Search, read, create, update, delete and the default distribution. |
| Bookkeeping Ledger Accounts | $twinfield->ledgerAccounts() |
Search and read all accounts and dimensions with their GUID (the uid of the dimension). See List ledger accounts. |
| Bookkeeping Audit Trails | $twinfield->matching()->auditTrail() |
The matches and undone matches since a moment. See Matching audit trail. |
| Customers, Suppliers | customers()->changes(), suppliers()->changes() |
Relations changed since a moment, also deleted and archived ones. See List changed customers. |
| Posting Rules | $twinfield->postingRules() |
Posting rules of customers and suppliers changed since a moment. See List posting rules. |
| VAT Codes | vatCodes()->searchDetails(), getDetails() |
VAT codes with GUIDs, rates over time and allocation rules. See List VAT code rates. |
| Feeds | $twinfield->bankFeeds()->psd2() |
PSD2 feeds and when their consent expires. See List PSD2 feeds. |
Other endpoints can be called directly; {organisationId} and {companyId} are filled in:
$json = $twinfield->rest('GET', '/organisations/{organisationId}/companies/{companyId}/vat/vatcodes', ['limit' => 10])->json(); foreach ($json->list('items') as $vatCode) { echo $vatCode->string('code'), PHP_EOL; }
Errors
Twinfield reports errors as SOAP faults; the SDK throws them as SoapFaultException. Besides the message, the exception has the fault detail, so you can act on an error without depending on the (language dependent) message:
use SolveBeam\Twinfield\Period\PeriodServiceFaultCode; use SolveBeam\Twinfield\Soap\SoapFaultException; try { $twinfield->periods()->list(1901); } catch (SoapFaultException $exception) { $exception->detailType; // 'PeriodServiceFault' (Command & Query services) $exception->detailCode; // 'YearNotFound', or e.g. '354' for classic services $exception->detailMessage; // 'Year 1901 bestaat niet.' $exception->source; // called method (classic services only) }
Match detailCode with ErrorCode for the general codes of the classic services (see Guidelines), e.g. ErrorCode::TokenInvalid, or with the codes of a Command & Query service such as PeriodServiceFaultCode.
Authorization errors come as HTTP 401 or 403; without a SOAP fault in the response the SDK throws an AccessDeniedException. Too many requests give a RateLimitExceededException; $twinfield->lastRateLimit() returns the rate limit headers of the most recent response, to slow down before that. Wrap the HTTP client in a RateLimitRetryClient to wait for Retry-After and retry automatically. See the rate limits guide.
See the error handling example for all error codes.
Backward compatibility
The SDK follows Semantic Versioning. The public API is what you use to work with Twinfield:
- the
Twinfieldclient and the resources it returns ($twinfield->customers(), …); - models, inputs, searches, enums and value objects (
Amount,Period, …); - exceptions;
- the low-level methods
processXml(),query(),process()andrest()with their results (ProcessXmlResult,ResultElement,RestResponse,JsonValue).
Classes and methods marked @internal are not part of it and can change in any release. That is how the SDK talks to Twinfield: the SOAP and REST clients, the XML and JSON readers and writers, the request classes, and the methods that map a model from or to XML or JSON (fromXml(), toXml(), …). Twinfield is moving from SOAP/XML to REST/JSON, and that layer moves with it. The examples print a request with toXml() to show what would be sent; read that output, don't build on it. PHPStan and PhpStorm report the use of internal code.
Nearly all classes are final: wrap them instead of extending them. The exceptions are base classes you catch, such as SoapFaultException and RestException.
PSR Compliance
PSR-1 (Basic Coding Standard)
The codebase follows PSR-1 fundamentals for PHP files and class structure.
PSR-2 (Coding Style Guide)
PSR-2 is superseded by PSR-12; this project follows PSR-12.
PSR-4 (Autoloading)
The SDK uses PSR-4 autoloading for the SolveBeam\Twinfield\ namespace, configured in composer.json.
PSR-6 (Caching)
Examples use PSR-6 caching for token storage via symfony/cache.
- https://www.php-fig.org/psr/psr-6/
- https://github.com/php-fig/cache
- https://packagist.org/packages/psr/cache
- https://github.com/symfony/cache
PSR-7 (HTTP Messages)
The OAuth2 provider relies on PSR-7 request/response interfaces for HTTP messages.
PSR-18 (HTTP Client)
The library can work with PSR-18 HTTP clients. Suggested implementations are listed in composer.json (e.g., symfony/http-client, php-http/curl-client) along with a PSR-7 implementation such as nyholm/psr7.
PSR-12 (Coding Style)
Coding style follows PSR-12, enforced via the PHPCS ruleset in phpcs.xml.dist.
OAuth2 Usage
<?php use SolveBeam\Twinfield\OAuth2\Provider\Twinfield; $provider = new Twinfield([ 'clientId' => 'your-client-id', 'clientSecret' => 'your-client-secret', 'redirectUri' => 'https://example.com/callback', ]); $authorizationUrl = $provider->getAuthorizationUrl(); // Redirect the user to $authorizationUrl, then handle the callback. $accessToken = $provider->getAccessToken('authorization_code', [ 'code' => $_GET['code'], ]); $resourceOwner = $provider->getResourceOwner($accessToken); $profile = $resourceOwner->toArray();
OpenID Connect Authentication
Scopes
The OpenID Connect scopes identify the user and keep the session; every API needs them:
| Scope | Description | Claims |
|---|---|---|
openid |
Standard OpenID Connect scope. Required when using identity-related scopes. | - |
twf.user |
Contains Twinfield-specific information about the user (identity token). Note: When adding this scope, add openid as well. |
• twf.id - GUID of Twinfield ID• twf.organisationUserCode - User code inside organisation• twf.organisationId - GUID of organisation |
twf.organisation |
Contains organisation information. | • twf.organisationCode - Code of organisation• twf.organisationId - GUID of organisation• twf.clusterUrl - URL of cluster on which organisation is located |
twf.organisationUser |
Contains information about organisation user. ⚠️ Important: This scope is mandatory in order to login. |
• twf.organisationUserCode - Code of the organisation user (used for login)• twf.organisationCode - Code of the organisation• twf.organisationId - GUID of organisation |
offline_access |
Required to receive refresh tokens in the authorization code flow. | - |
REST API scopes
Every operation of the REST API needs one of the read…/write… scopes on top of the identity scopes; without it Twinfield answers HTTP 403. The Scope enum has them all (Scope::rest() lists the REST API scopes); see the REST API guide for which endpoint needs which scope.
| Scope | Description | SDK |
|---|---|---|
readArticles |
Read sales articles. | salesArticles(): search, read, cultures, export |
writeArticles |
Create, update, delete and import sales articles (also reads them). | salesArticles(): create, update, delete, import |
readBankAccounts |
PSD2 bank feeds. | bankFeeds()->psd2() |
readBookkeeping |
Ledger accounts. | ledgerAccounts() |
readBookkeepingMatches |
Matching audit trail. | matching()->auditTrail(), auditTrailSince() |
readBudgets |
Budget lines, annual budgets and budget periods. | budgets(): lines, annual, periods, export |
writeBudgets |
Change, copy, spread and import budget lines (also reads them). | budgets(): line methods, copy, spread, import |
readBudgetScenarios |
Budgets and spreading templates. | budgets()->list(), getById(), spreadingTemplates() |
writeBudgetScenarios |
Create, update and delete budgets and spreading templates (also reads them). | budgets()->create(), update(), delete(), spreadingTemplates() |
readCustomers |
Changed customers, posting rules and ledger accounts. | customers()->changes(), postingRules() |
readSuppliers |
Changed suppliers, posting rules and ledger accounts. | suppliers()->changes(), postingRules() |
readFixedAssets |
Fixed assets of the new fixed assets module, with classes, groups and reports. | fixedAssets() |
readVat |
VAT codes. | vatCodes()->searchDetails(), getDetails() |
readVatGroup |
VAT groups; also gives access to the ledger accounts. | ledgerAccounts() |
Example with Scopes
<?php use SolveBeam\Twinfield\OAuth2\Provider\Twinfield; $provider = new Twinfield([ 'clientId' => 'your-client-id', 'clientSecret' => 'your-client-secret', 'redirectUri' => 'https://example.com/callback', ]); // Request authorization with specific scopes $authorizationUrl = $provider->getAuthorizationUrl([ 'scope' => ['openid', 'twf.user', 'twf.organisation', 'twf.organisationUser', 'offline_access'] ]); // Redirect the user to $authorizationUrl, then handle the callback. $accessToken = $provider->getAccessToken('authorization_code', [ 'code' => $_GET['code'], ]);
For more details, see the Twinfield OpenID Connect documentation.
Development and testing
One-time setup
Register http://localhost:8765/callback as redirect URI for your client in the Twinfield developer portal, then run:
composer twinfield:setup
This asks for your client ID, client secret and redirect URI, opens the Twinfield login in your browser and asks for a default company. Running any example for the first time does the same. Values are saved outside the project, in ~/.config/twinfield/ (only readable by you):
# ~/.config/twinfield/.env TWINFIELD_CLIENT_ID=… TWINFIELD_CLIENT_SECRET=… TWINFIELD_REDIRECT_URI=http://localhost:8765/callback TWINFIELD_COMPANY=1002 TWINFIELD_WRITE_COMPANIES=1002,1002_3 # optional, for write tests
The refresh token is stored in ~/.config/twinfield/token.json; access tokens are refreshed automatically.
An optional .env in the project root (gitignored) overrides values, and real environment variables override both. Set TWINFIELD_CONFIG_DIR to use another folder.
Commands
| Command | What it does |
|---|---|
composer test |
Offline unit tests on fixtures. Fast, no network. |
composer test:live |
Read-only tests against the live API (tests/Integration), including running every example. Skipped without login. |
composer test:write |
Write tests (tests/Write). Asks you to type the company code first; only companies in TWINFIELD_WRITE_COMPANIES. |
composer twinfield -- finder DIM "*" 10 |
Try a live call and see the SOAP request and response. Also periods [year], whoami, refresh, --company=CODE, --quiet. |
composer twinfield:setup |
Guided setup: credentials, login and default company. Run again to change them. |
composer twinfield:login |
Log in (again), e.g. when the refresh token has expired. Use -- --manual to paste the redirected URL, and -- --rest to also request the scopes of the REST API (tests of the REST API are skipped without them). |
composer twinfield:anonymize |
Turn recorded responses into anonymized fixtures. |
Environment switches
TWINFIELD_DEBUG=1: print every SOAP request and response (access token masked).TWINFIELD_RECORD=1: save raw responses totests/Fixtures/raw/(gitignored, contains real data).TWINFIELD_COMPANY=1002_3: run against another administration.
Fixtures from real responses
TWINFIELD_RECORD=1 composer test:live composer twinfield:anonymize git diff tests/Fixtures/soap
The anonymizer keeps the XML structure and replaces every value with a fake value in the same format (digits stay digits, dates stay valid dates). It uses a secret key in ~/.config/twinfield/anonymizer.key, so the same input always gives the same output. Check the list of kept values it prints before committing.
Safety
All live calls go through a write guard. Requests that may change data (anything that is not a search, get, query or ProcessXml read/list/browse) are blocked unless writes were confirmed for that company via composer test:write.
About
This library is made by SolveBeam. It is not an official Wolters Kluwer product. Twinfield is a product of Wolters Kluwer.
Twinfield and Wolters Kluwer are trademarks of Wolters Kluwer. Their names and logos are used only to refer to their products. See images/ for the sources of the logo files.
License
This library is released under the MIT License.
The license covers the code and documentation written by SolveBeam. Material from Twinfield in this repository, such as the WSDL files, the OpenAPI specification, the web services manuals, the browse definitions and the quoted knowledge base articles, remains the property of Wolters Kluwer and is included for reference only.