Frequently asked questions
What is darvis/supplydrive-api?
A Laravel package with a client for the Supplydrive API. Supplydrive is a Dutch PEPPOL access point and EDI provider; this package sends a UBL invoice to them and tells you whether it was delivered, by your invoice number, by the message id Supplydrive gave you, or per debtor. It does not build the UBL; darvis/ubl-peppol does that. It needs PHP 8.2+, Laravel 11, 12 or 13 and the accounts Supplydrive creates for you; the package itself is free and MIT licensed.
How do I connect a Laravel application to the Supplydrive portal?
Run composer require darvis/supplydrive-api, set SUPPLYDRIVE_USERNAME and SUPPLYDRIVE_PASSWORD in .env and check with php artisan supplydrive:status --functional-id=<invoice number>. Then resolve Darvis\SupplydriveApi\SupplydriveClient from the container and call byFunctionalId(), byMessageId(), byDebtorKvk(), byDebtorGln(), byDebtorVat() or messageStatus().
How does darvis/supplydrive-api log in, and where is the access token kept?
It posts grant_type=password, the client id SupplydrivePortal, your username and your password as a form to the token endpoint of the portal, and sends the token it gets in the Authorization header. The token is kept in memory and, encrypted with the application key, in Laravel’s cache until sixty seconds before it expires; SUPPLYDRIVE_TOKEN_CACHE=false keeps it in memory only.
Which statuses can a Supplydrive message have?
Eight, in Dutch: Ontvangen door supplydrive, In verwerking and Aflevering in retry are pending; Afgeleverd, Handmatig afgeleverd and Afgeleverd bij tussenpartij are delivered; Bericht kon niet afgeleverd worden and Mapping gefaald are failed. The MessageStatus enum has a case for each, and Message has isDelivered(), isFailed() and isPending(), so you never compare the Dutch strings yourself. description() explains a status in English or Dutch.
What happens when a lookup fails, and how do I get the HTTP status?
The client throws Darvis\SupplydriveApi\Exceptions\SupplydriveException, which extends RuntimeException, with a message such as Supplydrive API call failed. HTTP status: 403. Response: .... $e->status() gives the HTTP status, and 0 when there was no response. A 404 is not a failure: it means no messages match and gives an empty list. A timeout is an Illuminate\Http\Client\ConnectionException instead.
Does darvis/supplydrive-api retry a failed call?
Only once, and only for a 401 on a token it already had: it logs in again and repeats the call. A 401 on a fresh token, a 5xx and a timeout are not retried, so look statuses up from a scheduled command or a queued job. A request gives up after 30 seconds, 10 to connect (SUPPLYDRIVE_TIMEOUT, SUPPLYDRIVE_CONNECT_TIMEOUT). To see whether Supplydrive is up without sending anything, run php artisan supplydrive:health or call healthCheck() on the client or the sender; neither throws.
Is darvis/supplydrive-api safe with my portal password?
The account goes in the body of the token request and the token goes in a header, never in a URL. The package replaces the password and the token with [redacted] in its exception messages, log lines and command output, and it only stores the token in the cache encrypted, under a key that does not contain the username. The rest of an error response can name customers and invoices, so protect your logs.
Can Claude or another AI agent look up a Supplydrive status for me?
Yes. With laravel/mcp installed (Laravel Boost brings it) the package registers a local MCP server; start it with php artisan mcp:start supplydrive and add that command to your agent. The lookup-message-status tool takes the invoice number, the message id or a debtor and answers with every message, its status and whether that means delivered, failed or pending; explain-statuses lists the statuses. The server only reads.
How do I test code that uses darvis/supplydrive-api without calling Supplydrive?
Use Http::fake() for portal.supplydrive.com/realms/* (return an access_token) and for portal.supplydrive.com/SD/* (return a list of items with id, functionalId, status and interfaceId), set supplydrive.username and supplydrive.password to any value and call Http::preventStrayRequests(). Run the tests on the array cache store, so a cached token does not travel from one test to the next.
How do I send an invoice to Supplydrive from Laravel?
Set SUPPLYDRIVE_SEND_USERNAME, SUPPLYDRIVE_SEND_PASSWORD and, when yours differs from the default, SUPPLYDRIVE_SEND_URL in .env; it is another account than the portal login. Then call send($ublXml, $invoiceNumber) on Darvis\SupplydriveApi\SupplydriveSender. A 2xx gives a SendResult with the message id Supplydrive answered with; store it, it is the key for the status history. A refusal throws SupplydriveException, nothing is retried, and a timeout may still mean the document arrived, so look it up before you send again.