Skip to content

Repository files navigation

agent-dlocal

A read-only dLocal investigation and triage CLI built for AI agents.

dLocal is an emerging-markets payment processor (LatAm, Africa, Asia; payins and payouts). agent-dlocal turns "why did this payment fail?", "where did this payout go?", and "what happened to this refund?" into single commands that emit compact, structured, redacted output — so an LLM can answer them without ever seeing a credential.

It is the dLocal member of the agent-* family and shares its contracts with agent-stripe.

Install

brew install shhac/tap/agent-dlocal

Setup

agent-dlocal auth add prod --form
agent-dlocal auth check

--form collects the X-Login, X-Trans-Key, and Secret key through native OS dialogs — one per secret, each titled with the value it wants — so the secrets go straight from the user's keyboard into the OS keychain. They never appear in a chat transcript and never pass through a model's context. Values already given by flag are not prompted for again.

If you are automating, --login/--trans-key/--secret-key exist — but prefer --form for anything a human is present for.

Sandbox, and optional mutual TLS:

agent-dlocal auth add sbox --sandbox --form
agent-dlocal auth add prod --cert ~/.dlocal/client.pem --key ~/.dlocal/client.key --form

--cert/--key take paths; the files stay where you put them under your own permissions. The client key must be unencrypted PEM — Go's TLS loader cannot decrypt a passphrase-protected key, so decrypt it first with openssl pkey -in key.pem -out key-decrypted.pem.

dLocal keys carry no test/live marker, so live vs sandbox is a host distinction recorded explicitly on the profile — nothing is guessed from the credential.

Use

Start from the question, not the endpoint:

agent-dlocal investigate payment D-4-8f2a...     # Why did this payment fail?
agent-dlocal investigate order ORDER-10241       # They say they paid; our order says unpaid
agent-dlocal investigate refund REF-4471         # What happened to this refund?
agent-dlocal investigate payout P-2-91bc...      # Where is this payout?

Each returns a verdict, a terminal flag saying whether the state is final, next_steps, and the evidence it drew on.

Direct retrieval when you know what you want:

agent-dlocal payments get D-4-aaa D-4-bbb        # multiple ids, one record each
agent-dlocal payments status D-4-aaa             # status triple only
agent-dlocal orders get ORDER-10241              # merchant reference -> payment
agent-dlocal refunds get REF-4471
agent-dlocal chargebacks get CHAR42342
agent-dlocal payouts get P-2-91bc
agent-dlocal payment-methods list PH VN TH       # several markets at once
agent-dlocal payment-methods countries --supported  # which markets work at all
agent-dlocal api get /payments/D-4-aaa           # GET-only escape hatch

agent-dlocal usage prints the whole map; each group has its own usage too.

Working across countries

--country is a global flag, so switching market is the same flag on every command that takes one:

agent-dlocal --country PH auth check
agent-dlocal --country PH payment-methods list

payment-methods also takes countries positionally and repeatably, mirroring the multi-id get contract — one record per country, in input order:

$ agent-dlocal payment-methods list PH VN TH
{"country":"PH","count":14,"payment_methods":[...]}
{"country":"VN","count":14,"payment_methods":[...]}
{"country":"TH","count":13,"payment_methods":[...]}

Precedence is positional argument → --country → the profile's stored country. A country the merchant is not enabled for becomes a record carrying reason, so asking about ten markets and having one fail still answers the other nine.

Discovering markets

dLocal has no list-countries endpoint, so payment-methods countries probes each market and reports which resolve for your credentials — 43 markets in about a second:

$ agent-dlocal payment-methods countries --supported
{"country":"BR","supported":true,"payment_methods":49}
{"country":"MX","supported":true,"payment_methods":37}
...

Supported markets span South and Central America, South and Southeast Asia (including the Philippines, Indonesia, Vietnam, Thailand, Malaysia, India, Japan), and much of Africa. dLocal does not support Singapore, South Korea, Taiwan, Hong Kong, Venezuela, or western Europe.

Read-only by design

Every command is a GET. The raw api group has no --method flag — the read-only guarantee is the absence of a code path, not a check that could later be relaxed. dLocal refunds and payouts move real money in markets where reversal is slow, manual, or impossible.

Output contract

  • Lists stream NDJSON by default; --format json|yaml available.
  • get <id>... returns one record per id in input order. A miss emits an @unresolved line on stdout with exit 0, so one bad id does not lose the batch. Only command-level failures go to stderr with exit 1.
  • Sensitive fields are redacted by default — including payer.document, which is a national ID number (CPF, CUIT, DNI). --expose <path,key> opts out per invocation. Stored credentials are never exposable.
  • Errors are JSON on stderr: {"error", "fixable_by": "agent"|"human"|"retry", "hint"}.

Reading dLocal outcomes

dLocal reports a triple — status, status_code, status_detail. The detail carries the reason; the status only carries the category.

Payins: PENDING 100 · PAID 200 · REJECTED 300 · CANCELLED 400 · EXPIRED 600

Payouts: PENDING 100 · PAID 200 · REJECTED 300 · CANCELLED 400 · DELIVERED 500

DELIVERED is not final and not a failure — the money is in flight at the beneficiary's bank. It is the status most often misread, so investigate payout calls it out explicitly.

Read the dLocal code, not the HTTP status — they disagree. A bad signature is 400 {"code":5000} on payins and 403 {"code":"authentication_failed"} on payouts; there is no 401 on the payins paths at all. 403 {"code":3001} is returned before the signature is checked, so it means the caller was rejected outright — usually an IP allowlist or a wrong-host profile.

Clock skew is not a failure mode, despite X-Date being part of the signed message: the date is signed and sent, so a drifted clock stays self-consistent and validates.

MCP

agent-dlocal mcp

Exposes the agent-facing groups as MCP tools. auth, config, and usage are deliberately not exposed — credential management is an operator task, not something a tool-calling loop should reach.

Development

make build          # ./agent-dlocal
make test           # unit + e2e
make lint
make build-mock     # ./mockdlocal
make mock           # serve on 127.0.0.1:12112
make mock-dev ARGS="payments get D-4-rejected"
mockdlocal --routes # the mocked surface

mockdlocal verifies the HMAC signature on every request, so the e2e suite is evidence that signing works end to end rather than just that output parses.

See design-docs/initial-design.md for the endpoint inventory and the reasoning behind the auth model, and design-docs/mock-dlocal.md for the mock's contract.

Known gaps

  • Payouts v3 is not supported. It uses OAuth2 bearer tokens from /oauth/token rather than signatures, which is a second credential model. v2 covers the same read surface today.
  • No installments-plans retrieve command. Creating a plan is a mutation, and the GET retrieve-by-id form is not confirmed in dLocal's docs. api get covers it if it exists.

License

PolyForm Perimeter 1.0.0 — see LICENSE.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages