PHPackages                             oliverbj/cord - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [HTTP &amp; Networking](/categories/http)
4. /
5. oliverbj/cord

ActiveLibrary[HTTP &amp; Networking](/categories/http)

oliverbj/cord
=============

Seamless integration to CargoWise One's eAdapter using the HTTP service.

3.2.8(1mo ago)1174↓25%1[1 PRs](https://github.com/oliverbj/cord/pulls)MITPHPPHP ^8.2CI passing

Since Aug 23Pushed 1mo ago1 watchersCompare

[ Source](https://github.com/oliverbj/cord)[ Packagist](https://packagist.org/packages/oliverbj/cord)[ Docs](https://github.com/oliverbj/cord)[ RSS](/packages/oliverbj-cord/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (10)Dependencies (14)Versions (74)Used By (0)

Seamless integration to CargoWise One's eAdapter using Laravel
==============================================================

[](#seamless-integration-to-cargowise-ones-eadapter-using-laravel)

[![Latest Version on Packagist](https://camo.githubusercontent.com/ac4e6a05ef1a8a794940566d4115308d4f3b98f4eba999e83fcd7f5473ab8dc5/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6f6c69766572626a2f636f72642e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/oliverbj/cord)[![GitHub Tests Action Status](https://camo.githubusercontent.com/f91c5dc6706ad138299d71de8a331bf86e0a5aca64df7015471b8e63de1498f6/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f776f726b666c6f772f7374617475732f6f6c69766572626a2f636f72642f72756e2d74657374733f6c6162656c3d7465737473)](https://github.com/oliverbj/cord/actions?query=workflow%3Arun-tests+branch%3Amain)[![GitHub Code Style Action Status](https://camo.githubusercontent.com/733d4f5913e656a507e9c954d06142ae824260865cd6da5fc9980ae1c62d2bee/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f776f726b666c6f772f7374617475732f6f6c69766572626a2f636f72642f466978253230504850253230636f64652532307374796c652532306973737565733f6c6162656c3d636f64652532307374796c65)](https://github.com/oliverbj/cord/actions?query=workflow%3A%22Fix+PHP+code+style+issues%22+branch%3Amain)[![Total Downloads](https://camo.githubusercontent.com/9bc67063019cb0e200c463edf740353632c6b882fd7f6c262b570452a83f439e/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6f6c69766572626a2f636f72642e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/oliverbj/cord)

Cord offers an expressive, chainable API for interacting with CargoWise One's eAdapter over HTTP.

Table of Contents
-----------------

[](#table-of-contents)

- [Support](#support)
- [Installation](#installation)
- [Laravel Boost](#laravel-boost)
- [Configuration](#configuration)
- [Usage](#usage)
    - [Operation Schemas](#operation-schemas)
    - [Targets](#targets)
    - [Request context](#request-context)
- [Documents / eDocs](#documents--edocs)
    - [Upload documents](#upload-documents)
    - [Add events](#add-events)
- [Organizations](#organizations)
    - [Query Organization](#query-organization)
    - [Create Organization](#create-organization)
    - [Update Organization](#update-organization)
        - [Add an address](#add-an-address)
        - [Add a contact](#add-a-contact)
        - [Add EDI communication details](#add-edi-communication-details)
        - [Transfer existing organization data](#transfer-existing-organization-data)
- [Company](#company)
    - [Query Company](#query-company)
- [Staff](#staff)
    - [Query Staff](#query-staff)
    - [Create Staff](#create-staff)
    - [Update Staff](#update-staff)
- [One Off Quotes](#one-off-quotes)
    - [Query One-Off Quote](#query-one-off-quote)
    - [Create One-Off Quote](#create-one-off-quote)
    - [Update One-Off Quote](#update-one-off-quote)
- [Multiple Connections](#multiple-connections)
- [Raw XML](#raw-xml)
- [Response as JSON](#response-as-json)
- [Response as XML](#response-as-xml)
- [Field Selection](#field-selection)
- [Debugging](#debugging)
- [Testing](#testing)
    - [Manual staff test](#manual-staff-test)

Support
-------

[](#support)

Cord currently targets:

- PHP `8.2+`
- Laravel `11` and `12`

Installation
------------

[](#installation)

Install the package via Composer:

```
composer require oliverbj/cord
```

Publish the config file:

```
php artisan vendor:publish --tag="cord-config"
```

Laravel Boost
-------------

[](#laravel-boost)

If your application uses [Laravel Boost](https://laravel.com/docs/13.x/boost), Cord ships package-owned Boost resources so Boost can include Cord guidance automatically.

- Cord includes a core guideline that covers configuration, request flow, response handling, and package conventions.
- Cord also includes an optional `cord-development` skill for on-demand help with `describe()`, `schema()`, `fromStructured()`, `inspect()`, `toJson()`, `toXml()`, and `rawXml()`.

In the consuming Laravel application:

```
php artisan boost:install
```

After updating Cord or Boost itself, refresh generated agent resources with:

```
php artisan boost:update
```

Boost discovers these resources from the package automatically, so Cord does not require an extra publish step beyond Boost's normal install and update commands.

Configuration
-------------

[](#configuration)

The published `config/cord.php` file looks like this:

```
return [
    'base' => [
        'eadapter_connection' => [
            'url' => env('CORD_URL', ''),
            'username' => env('CORD_USERNAME', ''),
            'password' => env('CORD_PASSWORD', ''),
        ],
    ],
];
```

Set your CargoWise eAdapter credentials in `.env`:

```
CORD_URL=
CORD_USERNAME=
CORD_PASSWORD=
```

Usage
-----

[](#usage)

Start with a target, call `get()` before `run()` for organization, staff, and one-off quote retrievals, then execute the request with `run()`. By default Cord returns the decoded eAdapter payload as an array. Call `toJson()` or `toXml()` before `run()` when you need serialized output.

### Operation Schemas

[](#operation-schemas)

Cord now exposes AI-facing operation contracts and structured execution helpers.

```
use Oliverbj\Cord\Facades\Cord;

$schema = Cord::schema('one_off_quote.create');

$response = Cord::fromStructured('one_off_quote.create', [
    'company' => 'CPH',
    'branch' => 'A01',
    'department' => 'FES',
    'org_role' => 'LOC',
    'event_branch' => 'QTE',
    'event_department' => 'PRC',
    'transport_mode' => 'SEA',
    'port_of_origin' => 'AUSYD',
    'port_of_destination' => 'NZAKL',
    'client_address' => 'ABSDEOSLP',
])->run();
```

`describe()` is registry-backed:

- `Cord::describe()` lists all published resources and operation ids.
- `Cord::staff()->describe()` lists the operations for the selected resource.
- `Cord::staff('OJ0')->get()->describe()` returns the active JSON-Schema-style contract for staff retrieval.
- `Cord::staff()->create()->describe()` returns the active JSON-Schema-style contract for that fully scoped builder.

### Targets

[](#targets)

Cord currently supports these main targets:

- Bookings via `booking()`
- Shipments via `shipment()`
- One-off quotes via `oneOffQuote()`
- Customs declarations via `custom()`
- Organizations via `organization()`
- Companies via `company()`
- Containers via `container()`
- Staff via `staff()`
- Receivables / invoices via `receivable()` or `receiveable()` for document requests

```
use Oliverbj\Cord\Facades\Cord;

Cord::shipment('SMIA12345678')->run();

Cord::withCompany('CPH')
    ->oneOffQuote('QCPH00001004')
    ->get()
    ->run();

Cord::withCompany('CPH')
    ->oneOffQuote()
    ->create()
    ->branch('A01')
    ->department('FES')
    ->transportMode('SEA')
    ->portOfOrigin('AUSYD')
    ->portOfDestination('NZAKL')
    ->run();

Cord::custom('BATL12345678')->run();

Cord::organization('SAGFURHEL')->get()->run();

Cord::company('CPH')->run();
```

### Request context

[](#request-context)

You can scope a request to a specific CargoWise company:

```
Cord::shipment('SMIA12345678')
    ->withCompany('CPH')
    ->run();
```

For universal requests, `withCompany()` also enables Cord to derive a `SenderID` from the configured eAdapter host plus the company code, for example `DEMO1TRNCPH`. The default `RecipientID` is `Cord`.

If you need to override either value, you can set them explicitly:

```
Cord::shipment('SMIA12345678')
    ->withSenderId('PartnerA')
    ->withRecipientId('PartnerB')
    ->run();
```

Documents / eDocs
-----------------

[](#documents--edocs)

Most entities in CargoWise One expose eDocs. Use `withDocuments()` to retrieve a document collection for the selected target.

```
Cord::shipment('SMIA12345678')
    ->withDocuments()
    ->run();
```

When fetching documents you can add filters:

```
Cord::shipment('SMIA92838292')
    ->withDocuments()
    ->filter('DocumentType', 'ARN')
    ->filter('IsPublished', true)
    ->run();
```

Available filters:

- `DocumentType` retrieves only documents matching the specified document type.
- `IsPublished` retrieves only published or unpublished documents. Valid values are `true` and `false`.
- `SaveDateUTCFrom` retrieves only documents added or modified on or after the specified UTC timestamp.
- `SaveDateUTCTo` retrieves only documents added or modified on or before the specified UTC timestamp.
- `CompanyCode` retrieves only documents related to the specified company, or non-company-specific documents.
- `BranchCode` retrieves only documents related to the specified branch.
- `DepartmentCode` retrieves only documents related to the specified department.

### Upload documents

[](#upload-documents)

You can upload a document to a CargoWise file with `addDocument()`:

Document uploads are sent as `UniversalEvent` requests. When `withCompany()` is provided, Cord places `Company`, `EnterpriseID`, and `ServerID` inside `Event > DataContext` instead of using top-level interchange identifiers.

```
Cord::shipment('SJFK21060014')
    ->addDocument(
        file_contents: base64_encode(file_get_contents('myfile.pdf')),
        name: 'myfile.pdf',
        type: 'MSC',
        description: 'Optional description',
        isPublished: true,
    )
    ->run();
```

### Add events

[](#add-events)

Cord can also push events to jobs:

```
Cord::shipment('SJFK21060014')
    ->addEvent(
        date: now()->toIso8601String(),
        type: 'DIM',
        reference: 'My Reference',
        isEstimate: true,
    )
    ->run();
```

Organizations
-------------

[](#organizations)

Cord maps beautifully into the organization module of CargoWise.

### Query Organization

[](#query-organization)

Use `organization()` with one or more `criteriaGroup()` calls for native organization queries, then call `get()` before `run()`.

```
Cord::organization()
    ->criteriaGroup([
        [
            'Entity' => 'OrgHeader',
            'FieldName' => 'Code',
            'Value' => 'US%',
        ],
        [
            'Entity' => 'OrgHeader',
            'FieldName' => 'IsBroker',
            'Value' => 'True',
        ],
    ], type: 'Partial')
    ->get()
    ->run();
```

If the caller needs the organization payload as JSON instead of the default array, call `toJson()` before `run()`:

```
$json = Cord::organization()
    ->criteriaGroup([
        [
            'Entity' => 'OrgHeader',
            'FieldName' => 'Code',
            'Value' => 'SAGFURHEL',
        ],
    ], type: 'Key')
    ->get()
    ->toJson()
    ->run();
```

The `type` argument can be either `Key` or `Partial`. `Key` is the default.

#### Partial Match Retrieval

[](#partial-match-retrieval)

You can retrieve entities by providing field names along with either complete values or partial values using wildcards to filter by. Multiple criteria items can be provided and multiple groups of criteria can be provided.

All items within a criteria group assume an ‘And’ operation, whilst an ‘Or’ operation is performed between each criteria group.

You can define multiple criteria groups. Multiple groups behave like an `OR` statement:

```
Cord::organization()
    ->criteriaGroup([
        [
            'Entity' => 'OrgHeader',
            'FieldName' => 'Code',
            'Value' => 'US%',
        ],
    ], type: 'Partial')
    ->criteriaGroup([
        [
            'Entity' => 'OrgHeader',
            'FieldName' => 'IsBroker',
            'Value' => 'True',
        ],
    ], type: 'Partial')
    ->get()
    ->run();
```

#### Unique Key Based Retrieval

[](#unique-key-based-retrieval)

This is the retrieval of data by providing a candidate key (unique reference). This message requires that the Code property on the table OrgHeader is a candidate key to work.

If you specify a FieldName that is not a candidate key, a Rejection status will be returned with an appropriate error message.

When using unique key based retrieval, only a single key can be specified. There will only be one Criteria element, as multiple criteria with different unique keys would never return a result. This is because you could never find an Organization with a code of ABC and XYZ

You can define multiple criteria groups. Multiple groups behave like an `OR` statement:

```
Cord::organization()
    ->criteriaGroup([
        [
            'Entity' => 'OrgHeader',
            'FieldName' => 'Code',
            'Value' => 'ABCDEF',
        ],
    ], type: 'Key')
    ->get()
    ->run();
```

### Create Organization

[](#create-organization)

New organizations are created with `organization('CODE')->create()`. Supply at least `fullName()` — all other setters are optional. Multiple addresses and contacts can be chained.

```
Cord::withCompany('CPH')
    ->organization('NEWORG')
    ->create()
    ->fullName('New Organization Ltd')
    ->isActive(true)
    ->isForwarder(true)
    ->isConsignee(true)
    ->isConsignor(false)
    ->isAirLine(false)
    ->closestPort('AUSYD')
    ->addAddress(fn ($a) => $a
        ->code('MAIN ST')
        ->addressOne('1 Main Street')
        ->country('AU')
        ->city('Sydney')
        ->capability('OFC', isMainAddress: true)
    )
    ->addContact(fn ($c) => $c
        ->name('Operations')
        ->email('ops@example.com')
    )
    ->run();
```

MethodRequiredDescription`fullName(string)`✅Organisation display name`isActive(bool)`Defaults to `true``isConsignee(bool)`Organisation role flag`isConsignor(bool)`Organisation role flag`isForwarder(bool)`Organisation role flag`isAirLine(bool)`Organisation role flag`closestPort(string)`UNLOCO code for closest port`addAddress(Closure)`Repeatable; see address builder below`addContact(Closure)`Repeatable; see contact builder below> **Note:** `addAddress()` and `addContact()` use the same builders as the update path. See [Add an address](#add-an-address) and [Add a contact](#add-a-contact).

### Update Organization

[](#update-organization)

Native organization updates are anchored on `organization('CODE')->update()`. Call `update()` before any write setter — this mirrors the `staff()->update()` pattern.

#### Add an address

[](#add-an-address)

```
Cord::withCompany('CPH')
    ->organization('SAGFURHEL')
    ->update()
    ->addAddress(fn ($a) => $a
        ->code('MAIN STREET NO. 1')
        ->addressOne('Main Street')
        ->addressTwo('Number One')
        ->country('US')
        ->city('Anytown')
        ->state('NY')
        ->postcode('12345')
        ->relatedPort('USNYC')
        ->capability('OFC', isMainAddress: false)
    )
    ->run();
```

Required: `code`, `addressOne`, `country`, `city`.
Use `->capability($addressType, $isMainAddress)` to append an address type. Call it multiple times for multiple capabilities.

Available setters: `code`, `addressOne`, `addressTwo`, `country`, `city`, `state`, `postcode`, `relatedPort`, `phone`, `fax`, `mobile`, `email`, `dropModeFCL`, `dropModeLCL`, `dropModeAIR`, `active`, `capability`.

#### Add a contact

[](#add-a-contact)

```
Cord::withCompany('CPH')
    ->organization('SAGFURHEL')
    ->update()
    ->addContact(fn ($c) => $c
        ->name('Jane Doe')
        ->email('jane@example.com')
        ->phone('+1 555 123 4567')
        ->language('EN')
    )
    ->run();
```

Required: `name`, `email`.
Available setters: `name`, `email`, `active`, `notifyMode`, `title`, `gender`, `language`, `phone`, `mobilePhone`, `homePhone`, `attachmentType`.

#### Add EDI communication details

[](#add-edi-communication-details)

```
Cord::withCompany('CPH')
    ->organization('SAGFURHEL')
    ->update()
    ->addEDICommunication(fn ($e) => $e
        ->module('IMP')
        ->purpose('CUS')
        ->direction('OUT')
        ->transport('EML')
        ->destination('ops@example.com')
        ->format('XML')
    )
    ->run();
```

Required: `module`, `purpose`, `direction`, `transport`, `destination`, `format`.
Optional setters: `subject`, `publishMilestones`, `senderVAN`, `receiverVAN`, `filename`.

#### Transfer existing organization data

[](#transfer-existing-organization-data)

The transfer helpers copy an existing entity from a source organization payload to a target organization. They still accept a raw array sourced directly from a CargoWise payload:

- `transferAddress()`
- `transferContact()`
- `transferEDICommunication()`
- `transferDocumentTracking()`

```
$source = Cord::organization('SOURCE')->get()->run();

Cord::withCompany('CPH')
    ->organization('TARGET')
    ->update()
    ->transferContact($source['OrgContactCollection']['OrgContact'][0])
    ->run();
```

#### Schema and structured execution

[](#schema-and-structured-execution)

All organization write operations are registered in the operation registry, so `fromStructured()` and `schema()` work the same way as for One-Off Quotes and Staff:

```
// Introspect a specific operation
$schema = Cord::schema('organization.address.add');

// Execute via structured payload
$response = Cord::fromStructured('organization.contact.add', [
    'company' => 'CPH',
    'code' => 'SAGFURHEL',
    'contact' => [
        'name' => 'Jane Doe',
        'email' => 'jane@example.com',
    ],
])->run();
```

Company
-------

[](#company)

You are able to use Cord to interact with companies in CargoWise.

### Query Company

[](#query-company)

Company queries follow the same native query pattern:

```
Cord::company()
    ->criteriaGroup([
        [
            'Entity' => 'GlbCompany',
            'FieldName' => 'Code',
            'Value' => 'CPH',
        ],
    ])
    ->run();
```

Staff
-----

[](#staff)

You can also use Cord to manage Staff records in CargoWise.

Container
---------

[](#container)

Cord supports querying CargoWise container types via the native request pattern.

### Query Container

[](#query-container)

Container queries use `GlbContainerType` as the criteria entity. Call `get()` before `inspect()` or `run()`.

```
Cord::container()
    ->criteriaGroup([
        [
            'Entity' => 'GlbContainerType',
            'FieldName' => 'Code',
            'Value' => '20GP',
        ],
    ])
    ->get()
    ->run();
```

If you already know the container code, `container('20GP')->get()` preloads the same key criteria group.

Structured container queries work the same way:

```
$xml = Cord::fromStructured('container.query', [
    'criteria_groups' => [
        [
            'type' => 'Key',
            'criteria' => [
                ['entity' => 'GlbContainerType', 'field_name' => 'Code', 'value' => '20GP'],
            ],
        ],
    ],
])->inspect();
```

Staff
-----

[](#staff-1)

You can also use Cord to manage Staff records in CargoWise.

### Query Staff

[](#query-staff)

Staff queries follow the same native criteria-group pattern as organization queries, but use `GlbStaff` as the criteria entity. Call `get()` before `inspect()` or `run()`.

```
Cord::staff()
    ->criteriaGroup([
        [
            'Entity' => 'GlbStaff',
            'FieldName' => 'Code',
            'Value' => 'BVO',
        ],
    ], type: 'Key')
    ->get()
    ->run();
```

If you already know the staff code, `staff('BVO')->get()` preloads the same key criteria group for you.

Method introspection:

```
$schema = Cord::schema('staff.query');
$active = Cord::staff('BVO')->get()->describe();
```

### Create Staff

[](#create-staff)

Staff creation is sent as a native `Native` request. Company context is required. `EnterpriseID` and `ServerID` are derived from the configured `url`, and `CodesMappedToTarget` defaults to `true`. You can override the native context with `withEnterprise()`, `withServer()`, or `withCodeMapping(false)`.

```
Cord::withCompany('CPH')
    ->staff()
    ->create()
    ->code('BVO')
    ->loginName('user.test')
    ->password('1234')
    ->fullName('User Test')
    ->email('user.test@test.com')
    ->branch('TLS')
    ->department('FES')
    ->phone('+111')
    ->isActive(true)
    ->country('FR')
    ->replaceGroups(['ORGALL', 'OPSALL'])
    ->withPayload([
        'FriendlyName' => 'User Test',
        'Title' => 'Operations Specialist',
        'GlbWorkTime' => [
            '_attributes' => ['Action' => 'Insert'],
            'MondayWorkingHours' => '*******************',
            'TuesdayWorkingHours' => '*******************',
        ],
    ])
    ->run();
```

Common fluent setters:

- `code`, `loginName`, `password`, `fullName`, `branch`, `department`, and `country` are required for create requests.
- `withCompany('CPH')` is required for native staff create/update requests.
- `password(...)` automatically sets `ChangePasswordAtNextLogin` to `true`.
- `canLogin(...)` maps to CargoWise `CanLogin`. Create payloads default to `true` when omitted; update payloads only include it when you set it.
- new staff create payloads automatically include `IsOperational=true`.
- `replaceGroups([...])`, `addGroup(...)`, and `removeGroup(...)` are available for explicit group semantics.
- `withPayload([...])` can be used as a passthrough for CargoWise fields not yet wrapped by dedicated methods.
- `toPayload()` returns the compiled staff payload without sending a request.

Method introspection:

```
$schema = Cord::schema('staff.create');
$operations = Cord::staff()->describe();
```

`schema()` returns a JSON-Schema-style contract with `properties`, `required`, nested `items`, enums, and `x-cord` metadata for the operation id, resource, and action.

### Update Staff

[](#update-staff)

Staff updates are sent as native `Native` requests with `Action="UPDATE"`.

```
Cord::withCompany('CPH')
    ->staff('BVO')
    ->update()
    ->fullName('Updated User')
    ->canLogin(false)
    ->email('updated@example.com')
    ->branch('CPH')
    ->department('OPS')
    ->removeGroup('OLDOPS')
    ->addGroup('NEWOPS')
    ->phone('+4511223344')
    ->country('DK')
    ->withPayload([
        'FriendlyName' => 'Updated',
        'Title' => 'Branch Manager',
        'GlbWorkTime' => [
            '_attributes' => ['Action' => 'Update'],
            'MondayWorkingHours' => '********',
        ],
    ])
    ->run();
```

One Off Quotes
--------------

[](#one-off-quotes)

Use Cord to interact with the One-Off Quote module in CargoWise.

### Query One-Off Quote

[](#query-one-off-quote)

One-off quote retrieval uses the universal shipment request with `DataTarget Type="OneOffQuote"` and a quote key.

Call `withCompany()` before `run()` so Cord can populate the One-Off Quote `DataContext` with the company, `EnterpriseID`, `ServerID`, and the `ORP` recipient role expected by CargoWise.

```
$response = Cord::withCompany('CPH')
    ->oneOffQuote('QCPH00001004')
    ->get()
    ->run();
```

One-off quote query introspection:

```
$schema = Cord::schema('one_off_quote.get');
$active = Cord::oneOffQuote('QCPH00001004')->get()->describe();
```

### Create One-Off Quote

[](#create-one-off-quote)

One-off quote creation is sent as a universal shipment request with `DataTarget Type="OneOffQuote"`.

Call `withCompany()` before `run()` so Cord can populate the create `DataContext` with the company, required quote `branch()`, optional `orgRole()`, `EnterpriseID`, `ServerID`, and any optional `eventBranch()` / `eventDepartment()` codes.

```
Cord::withCompany('CPH')
    ->oneOffQuote()
    ->create()
    ->branch('A01')
    ->department('FES')
    ->orgRole('LOC')
    ->eventBranch('QTE')
    ->eventDepartment('PRC')
    ->transportMode('SEA')
    ->portOfOrigin('AUSYD')
    ->portOfDestination('NZAKL')
    ->serviceLevel('STD')
    ->incoterm('DAP')
    ->totalWeight(5000, 'KG')
    ->totalVolume(19.2, 'M3')
    ->goodsValue(15000, 'AUD')
    ->additionalTerms('Export Only')
    ->isDomesticFreight(false)
    ->clientAddress('ABSDEOSLP')
    ->pickupAddress(fn ($a) => $a
        ->addressLine1('3 TENTH AVENUE')
        ->city('OYSTER BAY')
        ->country('AU')
    )
    ->deliveryAddress('NZAKLDL1')
    ->addChargeLine(fn ($c) => $c
        ->chargeCode('FRT')
        ->description('International Freight')
        ->costAmount('500.0000', 'AUD')
        ->sellAmount('1500.0000', 'AUD')
    )
    ->addPackLine(fn ($p) => $p
        ->packageType('BOX')
        ->quantity(10)
        ->weight(500, 'KG')
        ->volume(2.5, 'M3')
    )
    ->addAttachedDocument(fn ($d) => $d
        ->fileName('Quote.pdf')
        ->imageData(base64_encode(file_get_contents('Quote.pdf')))
        ->type('QTE')
        ->isPublished(true)
    )
    ->withPayload([
        'CustomizedFieldCollection' => [
            'CustomizedField' => [
                'DataType' => 'String',
                'Key' => 'Test User',
                'Value' => 'Janice Testing',
            ],
        ],
    ])
    ->run();
```

The address setters accept either a full nested address builder or a CargoWise organization code string such as `ABSDEOSLP`.

Structured one-off quote create payloads support the same shortcuts:

```
$xml = Cord::fromStructured('one_off_quote.create', [
    'company' => 'CPH',
    'branch' => 'A01',
    'department' => 'FES',
    'org_role' => 'LOC',
    'event_branch' => 'QTE',
    'event_department' => 'PRC',
    'transport_mode' => 'SEA',
    'port_of_origin' => 'AUSYD',
    'port_of_destination' => 'NZAKL',
    'client_address' => 'ABSDEOSLP',
    'pickup_address' => [
        'address_line_1' => '3 TENTH AVENUE',
        'city' => 'OYSTER BAY',
        'country' => 'AU',
    ],
    'delivery_address' => 'NZAKLDL1',
    'pack_lines' => [
        [
            'pack_type' => 'BOX',
            'quantity' => 10,
            'weight' => ['value' => 500, 'unit_code' => 'KG'],
            'volume' => ['value' => 2.5, 'unit_code' => 'M3'],
        ],
    ],
])->inspect();
```

One-off quote create requirements:

- `withCompany(...)`
- `branch(...)`
- `department(...)`
- `transportMode(...)`
- `portOfOrigin(...)`
- `portOfDestination(...)`

Optional one-off quote create helpers:

- `branch(...)` maps to both `Shipment > DataContext > Branch > Code` and `Shipment > JobCosting > Branch > Code`.
- `orgRole(...)` maps to `Shipment > DataContext > OrgRole`; use `LOC` for Local Client or `OAG` for Overseas Agent.
- `eventBranch(...)` maps to `Shipment > DataContext > EventBranch > Code`.
- `eventDepartment(...)` maps to `Shipment > DataContext > EventDepartment > Code`.
- `clientAddress(...)`, `pickupAddress(...)`, and `deliveryAddress(...)` accept either an address object or a plain organization code string.
- When passing an address object, `address_line_1` is required unless `address_override` is set to `true`. With `address_override: true`, only `city` and `country` are required — useful for sending a partial address without a street line.
- In structured payloads, use `org_role`, `event_branch`, `event_department`, and either an address object or a plain string for the address fields.
- `addPackLine(...)` adds individual packing lines with `pack_type` (required), `quantity` (required), and optional `weight`, `volume`, `length`, `width`, `height`, and `description`.
- In structured payloads, use `pack_lines` as an array of objects with `pack_type`, `quantity`, and dimension sub-objects such as `weight => ['value' => 500, 'unit_code' => 'KG']`.
- `addContainer(...)` adds individual containers with `type` (required, e.g. `20GP`), optional `count` (defaults to `1`), `type_description`, `iso_code`, and `category` (`['code' => 'DRY', 'description' => 'Dry Storage']`). Maps to `ContainerCollection > Container` in XML. Use this for FCL shipments.
- In structured payloads, use `containers` as an array of objects with `type` and the optional fields above.
- `addChargeLine(...)` adds individual charge lines. Required setters are `chargeCode` and `description`; optional setters are `costAmount(value, currencyCode)`, `sellAmount(value, currencyCode)`, `chargeCodeGroup`, `branch`, `department`, `debtor`, and `displaySequence`. Only the fields you provide appear in the outgoing XML — no rating behaviours, exchange rates, or posted flags are hard-coded. CargoWise eAdapter requires an `ImportMetaData` element on every charge line when importing; Cord adds `INSERT` automatically.
- In structured payloads, use `charge_lines` as an array of objects with `charge_code`, `description`, and optional `cost_amount` / `sell_amount` (each with `value` and `currency_code`), `charge_code_group`, `branch`, `department`, `debtor`, and `display_sequence`.

One-off quote introspection:

```
$querySchema = Cord::schema('one_off_quote.get');
$query = Cord::oneOffQuote('QCPH00001004')->get()->describe();

$schema = Cord::schema('one_off_quote.create');
$active = Cord::oneOffQuote()->create()->describe();

$updateSchema = Cord::schema('one_off_quote.update');

$docSchema = Cord::schema('one_off_quote.document.add');
```

### Update One-Off Quote

[](#update-one-off-quote)

One-off quote updates are sent as a sparse `UniversalShipment` request. Only the fields you set are included — all setters are optional.

Call `withCompany()` before `run()` so Cord can populate the update `DataContext` with the company, `EnterpriseID`, and `ServerID`. The quote key passed to `oneOffQuote()` is written into `DataTargetCollection > DataTarget > Key`.

```
Cord::withCompany('CPH')
    ->oneOffQuote('QCPH00001004')
    ->update()
    ->transportMode('AIR')
    ->portOfOrigin('AUSYD')
    ->portOfDestination('GBLON')
    ->serviceLevel('EXP')
    ->run();
```

Exact same fluent setters as create are available — `transportMode`, `portOfOrigin`, `portOfDestination`, `serviceLevel`, `incoterm`, `totalWeight`, `totalVolume`, `goodsValue`, `additionalTerms`, `isDomesticFreight`, `branch`, `department`, `orgRole`, `eventBranch`, `eventDepartment`, `clientAddress`, `pickupAddress`, `deliveryAddress`, `addChargeLine`, `addPackLine`, `addContainer`, `addAttachedDocument`, and `withPayload`. Only the setters you call will appear in the outgoing XML.

Structured equivalent:

```
Cord::fromStructured('one_off_quote.update', [
    'company' => 'CPH',
    'key' => 'QCPH00001004',
    'transport_mode' => 'AIR',
    'port_of_origin' => 'AUSYD',
    'port_of_destination' => 'GBLON',
    'service_level' => 'EXP',
])->run();
```

Requirements:

- `withCompany(...)` — required.
- A quote key passed to `oneOffQuote('KEY')` — required.

Introspection:

```
$schema = Cord::schema('one_off_quote.update');
$active = Cord::oneOffQuote('QCPH00001004')->update()->describe();
```

### Add Document to One-Off Quote

[](#add-document-to-one-off-quote)

Add a document to an existing one-off quote with `addDocument()`. This uses `UniversalEvent` rather than `UniversalShipment`. Call `withCompany()` so Cord can populate `Event > DataContext` with `Company`, `EnterpriseID`, and `ServerID` for the target quote key.

```
Cord::withCompany('CPH')
    ->oneOffQuote('QCPH00001004')
    ->addDocument(
        file_contents: base64_encode(file_get_contents('quote.pdf')),
        name: 'quote.pdf',
        type: 'QUO',
        description: 'Signed quote',
        isPublished: true,
    )
    ->run();
```

Structured equivalent:

```
Cord::fromStructured('one_off_quote.document.add', [
    'company' => 'CPH',
    'key' => 'QCPH00001004',
    'document' => [
        'file_contents' => base64_encode(file_get_contents('quote.pdf')),
        'name' => 'quote.pdf',
        'type' => 'QUO',
        'description' => 'Signed quote',
        'is_published' => true,
    ],
])->run();
```

Multiple Connections
--------------------

[](#multiple-connections)

If you need to connect to multiple eAdapters, use `withConfig()`:

```
Cord::shipment('SJFK21060014')
    ->withConfig('archive')
    ->run();
```

Then add the additional connection to `config/cord.php`:

```
return [
    'base' => [
        'eadapter_connection' => [
            'url' => env('CORD_URL', ''),
            'username' => env('CORD_USERNAME', ''),
            'password' => env('CORD_PASSWORD', ''),
        ],
    ],

    'archive' => [
        'eadapter_connection' => [
            'url' => env('CORD_ARCHIVE_URL', ''),
            'username' => env('CORD_ARCHIVE_USERNAME', ''),
            'password' => env('CORD_ARCHIVE_PASSWORD', ''),
        ],
    ],
];
```

The configured URL does not have to point directly at the eAdapter itself. It can point to middleware, as long as that middleware forwards the request and returns the eAdapter response. If you want enterprise and server auto-detection for native write requests, the URL should preserve the CargoWise host pattern such as `https://demo1trnservices.example.invalid/eAdaptor`.

Raw XML
-------

[](#raw-xml)

If you already have a complete XML payload and just want Cord to send it with the configured eAdapter credentials and headers, use `rawXml()`:

```
$response = Cord::withConfig('NTG_TRN')
    ->rawXml($xml)
    ->run();
```

For raw XML requests, `run()` returns the full parsed eAdapter envelope instead of only `Data`. That means `Status`, `ProcessingLog`, and `Data` are all preserved.

```
$status = $response['Status'];
$message = $response['ProcessingLog'] ?? null;
```

`inspect()` still performs a dry run and returns the outbound XML unchanged:

```
$xml = Cord::rawXml($xml)->inspect();
```

Response as JSON
----------------

[](#response-as-json)

If you want the same response payload serialized as JSON, call `toJson()` before `run()`:

```
$json = Cord::shipment('SJFK21041242')
    ->toJson()
    ->run();
```

For `rawXml()` requests, `toJson()` returns the full response envelope as JSON, including `Status`, `ProcessingLog`, and `Data`:

```
$json = Cord::rawXml($xml)
    ->toJson()
    ->run();
```

Response as XML
---------------

[](#response-as-xml)

If you want the original eAdapter response as XML, call `toXml()` before `run()`:

```
Cord::shipment('SJFK21041242')
    ->toXml()
    ->run();
```

For `rawXml()` requests, `toXml()` returns the full response envelope, including `Status` and `ProcessingLog`:

```
$response = Cord::rawXml($xml)
    ->toXml()
    ->run();
```

Field Selection
---------------

[](#field-selection)

Chain `select()` before `run()` on any query to restrict the returned payload to just the fields you need:

```
// Varargs — top-level fields
$result = Cord::container()
    ->criteriaGroup([
        [
            'Entity' => 'GlbContainerType',
            'FieldName' => 'ShippingMode',
            'Value' => 'SEA',
        ],
    ], type: 'Partial')
    ->get()
    ->select('Code', 'ShippingMode')
    ->run();

// Single array argument
->select(['Code', 'ShippingMode', 'Description'])

// Dot-notation for nested fields
->select('Code', 'Owner.Code')
```

`select()` works across all response types: flat single-record responses, multi-record lists, and universal shipment payloads. It also composes with `toJson()`:

```
$json = Cord::organization()
    ->criteriaGroup([...])
    ->get()
    ->select('Code', 'FullName')
    ->toJson()
    ->run();
```

Debugging
---------

[](#debugging)

Use `inspect()` to build and inspect the outgoing XML without sending the HTTP request:

```
$xml = Cord::custom('BJFK21041242')
    ->withDocuments()
    ->filter('DocumentType', 'ARN')
    ->inspect();

return response($xml, 200, ['Content-Type' => 'application/xml']);
```

Testing
-------

[](#testing)

```
composer test
```

`composer test` now covers both staff creation and non-collection staff updates.

### Manual staff test

[](#manual-staff-test)

For controlled manual testing, Cord includes a local runner script that builds the staff payload and only sends it when you explicitly opt in. By default it creates staff; add `--update` to send an update instead.

1. Create a local `.env` from `.env.example` and fill in your connection details.
2. Copy `resources/manual/staff-payload.example.php` to `resources/manual/staff-payload.local.php`.
3. Edit the local payload file with the staff data you want to test.
4. Run an inspect-only dry run first:

```
composer manual-staff-test -- --connection=NTG_TRN --company=CPH
```

That prints the exact XML and does not send anything.

When you are ready to post the request, run:

```
composer manual-staff-test -- --connection=NTG_TRN --company=CPH --send
```

To update an existing staff row such as `XX0`, keep the `code` in your payload and add `--update`:

```
composer manual-staff-test -- --connection=NTG_TRN --payload=resources/manual/staff-payload.local.php --update
composer manual-staff-test -- --connection=NTG_TRN --payload=resources/manual/staff-payload.local.php --update --send
```

Example local `.env`:

```
CORD_URL=https://demo1trnservices.example.invalid/eAdaptor
CORD_USERNAME=your-eadapter-user
CORD_PASSWORD=xxxx
```

The runner derives `EnterpriseID=XXX` and `ServerID=TRN` from that URL and uses them in the native `DataContext`.

You can still override the derived native context per run if needed:

```
composer manual-staff-test -- \
  --connection=MY_TRN \
  --company=CPH \
  --enterprise=XXX \
  --server=TRN
```

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](https://github.com/oliverbj/.github/blob/main/CONTRIBUTING.md) for details.

Security Vulnerabilities
------------------------

[](#security-vulnerabilities)

Please review [our security policy](../../security/policy) on how to report security vulnerabilities.

Credits
-------

[](#credits)

- [Oliver Busk](https://github.com/oliverbj)
- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

52

—

FairBetter than 96% of packages

Maintenance93

Actively maintained with recent releases

Popularity17

Limited adoption so far

Community11

Small or concentrated contributor base

Maturity74

Established project with proven stability

 Bus Factor1

Top contributor holds 90.8% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~25 days

Recently: every ~2 days

Total

54

Last Release

36d ago

Major Versions

1.3.1 → 2.0.02023-06-20

2.0.1 → 3.0.02026-03-18

PHP version history (2 changes)1.0.0PHP ^8.1

3.0.0PHP ^8.2

### Community

Maintainers

![](https://www.gravatar.com/avatar/b7fd3189139a424ee43807e351408ff8af2d8432f3ac28c8e85c23a63ba2da07?d=identicon)[oliverbj](/maintainers/oliverbj)

---

Top Contributors

[![oliverbj](https://avatars.githubusercontent.com/u/7851696?v=4)](https://github.com/oliverbj "oliverbj (324 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (22 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (11 commits)")

---

Tags

laravelcordoliverbj

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/oliverbj-cord/health.svg)

```
[![Health](https://phpackages.com/badges/oliverbj-cord/health.svg)](https://phpackages.com/packages/oliverbj-cord)
```

###  Alternatives

[omniphx/forrest

A Laravel library for Salesforce

2724.4M8](/packages/omniphx-forrest)[bilfeldt/laravel-http-client-logger

A logger for the Laravel HTTP Client

1531.6M3](/packages/bilfeldt-laravel-http-client-logger)[tzsk/sms

A robust and unified SMS gateway integration package for Laravel, supporting multiple providers.

320244.3k6](/packages/tzsk-sms)[sunchayn/nimbus

A Laravel package providing an in-browser API client with automatic schema generation, live validation, and built-in authentication with a touch of Laravel-tailored magic for effortless API testing.

29428.0k](/packages/sunchayn-nimbus)[muhammadhuzaifa/telescope-guzzle-watcher

Telescope Guzzle Watcher provide a custom watcher for intercepting http requests made via guzzlehttp/guzzle php library. The package uses the on\_stats request option for extracting the request/response data. The watcher intercept and log the request into the Laravel Telescope HTTP Client Watcher.

98239.8k1](/packages/muhammadhuzaifa-telescope-guzzle-watcher)[api-platform/laravel

API Platform support for Laravel

59126.4k6](/packages/api-platform-laravel)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
