TRIDENT by Marvera

Docs · integration

Two endpoints, and what each one will and will not accept

One reads charter context so an external system knows what it is posting against. The other stages APA expenses and promotes them into draft entries through the same validation a person goes through. That is the entire surface. If it is not on this page, it is not exposed.

build 0.1.0.26 · pre-release

Read this before you build against it

Every field on this page is held by a committed contract. A gate on every change reads the shape back out of the source and fails if it has drifted — a renamed field, a bumped version, a dropped promote, or a field quietly repointed at different data behind an unchanged name. A rename cannot reach you by accident. It can only reach you deliberately, in a change that rewrites the contract, and that is exactly the change worth arguing with me about.

What the gate does not do is call the API. It reads source, so nothing here is proven against a live tenant by an automated test. Verify your first round trip against a sandbox, and tell me you exist so a deliberate change gets discussed with you rather than announced at you.

One thing about that gate you should have straight before you rely on it: it is written, green and tested against a deliberately broken copy of itself, but it is still an open change rather than part of the main line. Today it holds the change that carries it, not every change. The limitations page tracks which of these have landed and which have not, and it will say so there before it says so anywhere else.

Endpoint shape

Both pages publish under /api/marvera/trident/v2.0/.

On Business Central online:

https://api.businesscentral.dynamics.com/v2.0/{tenantId}/{environment}/api/marvera/trident/v2.0/companies({companyId})/{entitySet}

On a self-hosted or container environment:

https://{server}:{port}/{instance}/api/marvera/trident/v2.0/companies({companyId})/{entitySet}

Company ids come from the standard Business Central companies endpoint. Both pages key on SystemId, so a record is addressed as {entitySet}({systemId}). Both are marked non-extensible: no other extension can add fields to them, so the shape you integrate against is the shape that ships.

Authentication is standard Business Central. Trident adds nothing to it and overrides nothing.


Permissions

Give the integration's service account Trident API and nothing else from Trident. It is not included in Admin, Ops or Read, and none of them include it.

Trident API · permission set 70241019
ObjectGrantWhy it is there
APA Expense Import MVRead, Insert, ModifyThe staging table the integration writes to
APA Ledger Entry MVIndirect read, indirect insertPromotion can create the entry; the account cannot write one directly
Charter Header MVReadCharter context, and checking that a staged charter number exists
APA Expense Category MVReadChecking the category code, and whether it is blocked
CurrencyReadChecking the currency code
General Ledger SetupReadThe local currency code, read when the currency snapshot is taken
Currency Exchange RateReadConverting the expense amount at its posting date

The indirect grant on the APA ledger is the part worth understanding. The service account cannot insert an APA entry directly. It can only cause one to exist by calling promote, which runs the same validation the journal runs. An integration cannot write an entry a person could not have written.

The last two rows are why a first attempt often fails with a permission error that looks unrelated to APA. Promotion snapshots the currency position at the posting date, which reads the local currency code and the rate.


charterContexts · read only

GET /companies({companyId})/charterContexts

Read-only in the strictest sense the platform allows: insert, modify and delete are disabled on the page and every field is non-editable. There is no way to change a charter through this endpoint.

Fields
FieldTypeNotes
systemIdGUIDThe key
nostringThe charter number
descriptionstring
statusenumOpen, Option, Confirmed, Invoiced, Completed, Cancelled, Active, Closed
currencyCodestringThe charter's currency
apaRequiredbooleanWhether the charter's pricing scheme requires APA
startDatedate
endDatedate

Use it to resolve a charter number before staging anything against it, to check apaRequired before sending APA at all, and to avoid posting into a charter that has been cancelled or closed. Standard OData filtering applies.


apaExpenseImports · staging and promotion

GET, POST and PATCH on /companies({companyId})/apaExpenseImports.

Insert one row per expense, then call promote on it. Rows are staged, not posted. Nothing reaches the APA ledger until promotion succeeds.

Fields · you write the first group, Trident writes the second
FieldTypeRule
externalSourcestring, 40Required, not blank. The system the expense came from
externalIdstring, 100Required, not blank. Your identifier for the expense
charterNostring, 20Must match an existing charter
categoryCodestring, 20Must match an APA category that is not blocked
postingDatedateRequired
amountdecimalMust be greater than zero
currencyCodestring, 10Optional. If supplied it must exist
descriptionstring, 100
vendorNamestring, 100
correlationIdstring, 100Free-text trace reference, carried through untouched
importStatusenumRead-only. Received, Rejected, Promoted, Duplicate
errorDetailsstring, 250Read-only. Why a row was rejected
promotedEntryNointegerRead-only. The APA entry number created
systemIdGUIDRead-only. The key

A newly inserted row is Received. Trident also stamps the arrival time and the account that sent it; neither is exposed on the API page.

Promoting

POST /companies({companyId})/apaExpenseImports({systemId})/Microsoft.NAV.promote

Empty body. On success the staged row becomes a draft APA entry, not a reviewed one and not a posted one. It appears in the APA journal exactly as if someone had keyed it, and a person still reviews it.

Read the row back. Do not trust the HTTP status

The action reports success whenever it completes, including when it decided the row was invalid or a duplicate. importStatus is the real outcome, not the response code. This is the most common way to build a broken integration against this endpoint.

The four outcomes
importStatusWhat happenedWhat to do
PromotedA draft APA entry was created and promotedEntryNo holds its numberNothing. Record the entry number on your side
RejectedValidation failed. errorDetails lists every reason, separated by semicolonsCorrect the row with PATCH and promote again. A corrected row promotes normally
DuplicateAnother row with the same source and external id was already promotedNothing. This is the guard working. Do not retry
ReceivedUnchanged, so the call did not reach promotionInvestigate. This should not happen

Promoting a row that is already promoted does nothing and creates nothing, so retrying is safe.

What gets rejected

Every failing check is collected before anything is written, so errorDetails gives you all the problems at once rather than the first one: charter number missing or not found; category missing, not found, or blocked; posting date missing; amount not greater than zero; and a currency code that was supplied but does not exist.

Duplicate handling

The guard is externalSource plus externalId, and it only fires against rows that were actually promoted. Two rows carrying the same pair can both sit in staging; the second one promoted becomes Duplicate rather than creating a second APA entry.

So you can retry a failed send without deduplicating on your side, as long as the same expense always carries the same externalId. Send a different one and you get two APA entries, and Trident will not know they were the same expense.


What is not exposed

No endpoint exists for writing charters, or for vessels, payment templates, payment schedules, settlement, closeout, commercial evidence, or the APA ledger itself. Reading APA entries back is not available through the API; the ledger is reachable through the Business Central client and through the finance review report.

Bulk promotion exists in the code behind the endpoint and is not published, because nobody has needed it yet.

If your integration needs one of these, say which and why. Adding an endpoint is cheap. Adding the wrong one and supporting it forever is not. Write to me.