# mrktplaats CLI

Command-line interface to [marktplaats.nl](https://www.marktplaats.nl), built on the
[mrktplaats Go SDK](../mrktplaats). Designed to be used by both humans and LLM agents.

---

## Build

```bash
cd mrktplaats-cli
go build -o mrktplaats .
# optional: install globally
go install .
```

---

## Authentication

Run `login` once to authenticate. The token is saved to
`~/.config/mrktplaats/token` and reused automatically.

```bash
mrktplaats login --email you@example.com --password yourpassword
```

If the account has SMS 2FA enabled the first call returns a verification prompt.
Re-run with the code you received:

```bash
mrktplaats login --email you@example.com --password yourpassword --code 123456
```

**Alternative: environment variable**

```bash
export MRKTPLAATS_TOKEN=your-token-here
mrktplaats me
```

---

## Output format

All commands write **pretty-printed JSON** to stdout.
Errors are written to stderr and exit with code 1.

---

## Command reference

### `login`

```
mrktplaats login --email EMAIL --password PASS [--code 2FA_CODE]
```

Authenticate and persist the token. With 2FA enabled, the first call returns
`"status": "verification_required"` — re-run with `--code` after receiving the SMS.

**Output (success)**
```json
{
  "status": "ok",
  "token_saved": "/home/you/.config/mrktplaats/token",
  "access_token": "8fc90f77-..."
}
```

**Output (2FA required)**
```json
{
  "status": "verification_required",
  "method": "sms",
  "message": "We sent a code to +32 4xx xxx xxx",
  "request_id": "abc123",
  "next_step": "re-run with --code CODE after receiving the SMS"
}
```

---

### `search`

```
mrktplaats search --query Q [--category ID] [--page N] [--size N] [--sort FIELD]
```

Search listings. No authentication required.

| Flag | Default | Description |
|------|---------|-------------|
| `--query` | *(required)* | Search query |
| `--category` | 0 (all) | Category ID filter |
| `--page` | 1 | Page number |
| `--size` | 20 | Results per page (max 100) |
| `--sort` | `SORT_INDEX` | `SORT_INDEX` \| `DATE_DESC` \| `PRICE_ASC` \| `PRICE_DESC` |
| `--seller` | — | Filter by seller ID |

**Output**
```json
{
  "total": 2573,
  "page": 1,
  "size": 3,
  "listings": [
    {
      "urn": "m2372861012",
      "title": "Seiko SARV001",
      "price_cents": 25000,
      "price_label": "€ 250,00",
      "price_type": "FIXED",
      "city": "Gent",
      "seller_name": "Jonathan",
      "url": "https://..."
    }
  ]
}
```

`price_type` values: `FIXED`, `MIN_BID`, `FAST_BID`, `FREE`, `ON_REQUEST`.

---

### `listing`

```
mrktplaats listing --urn URN
```

Fetch full details for a listing. Requires authentication.

**Output** — full listing object including:
- `adCore.title`, `.description`, `.price`, `.adAddress`, `.pictures`, `.attributes`
- `sellerInformation.id`, `.name` — seller's ID is needed for `Relevant.Get`
- `bids` — array of current bids
- `currentMinimumBid` — minimum next bid in cents

---

### `conversations`

```
mrktplaats conversations [--limit N]
```

List all messaging conversations.

**Output**
```json
{
  "total": 2,
  "unread": 0,
  "conversations": [
    {
      "id": "1cgx:5qgvx3h:2p1gxt95d",
      "item_urn": "m2372861012",
      "title": "NVIDEA 100000 V2",
      "other_party": "Mattia",
      "other_party_id": 57506580,
      "unread": 0,
      "latest_message": "Is this still available?"
    }
  ]
}
```

---

### `messages`

```
mrktplaats messages --conv CONV_ID [--limit N] [--offset N]
```

Read messages in a conversation. Use `conversations` to find IDs.

**Output**
```json
{
  "total": 4,
  "messages": [
    {
      "id": "abc123",
      "sender_id": 40921019,
      "text": "Is this still available?",
      "date": "2026-03-03T18:48:00Z",
      "type": "chat",
      "read": false
    }
  ]
}
```

---

### `send`

```
mrktplaats send --urn URN --text TEXT
```

Start a **new** conversation with the seller of a listing.
Use `reply` to respond to an existing conversation.

**Output**
```json
{
  "conversation_id": "1cgx:5qgvx3h:2p1gxt95d",
  "item_urn": "m2372861012",
  "status": "sent"
}
```

---

### `reply`

```
mrktplaats reply --conv CONV_ID --text TEXT
```

Send a message in an **existing** conversation.

**Output**
```json
{
  "message_id": "029c3014-...",
  "conversation_id": "1cgx:5qgvx3h:2p1gxt95d",
  "status": "sent"
}
```

---

### `bid`

```
mrktplaats bid --urn URN --amount EUROS [--message TEXT]
```

Place a bid. The listing must have `price_type` of `MIN_BID` or `FAST_BID`.
Amount is in **euros** (e.g. `--amount 20` for €20.00).

**Output**
```json
{
  "bids": [
    {"id": 1533844324, "value": 2000, "date": "...", "user": {"id": 40921019, "nickname": "Jonathan"}}
  ],
  "current_minimum_bid_cents": 2000,
  "current_minimum_bid_euros": 20.0
}
```

---

### `remove-bid`

```
mrktplaats remove-bid --id BID_ID
```

Remove a bid. `BID_ID` is the numeric `id` field from the `bids` array.

**Output**
```json
{
  "bids": [],
  "current_minimum_bid_cents": 2000,
  "current_minimum_bid_euros": 20.0
}
```

---

### `favorites`

```
mrktplaats favorites [--limit N]
```

List saved listings.

**Output**
```json
{
  "total": 3,
  "more": false,
  "items": [
    {"item_urn": "m2372861012", "saved_at": ""}
  ]
}
```

---

### `add-favorite` / `remove-favorite`

```
mrktplaats add-favorite --urn URN
mrktplaats remove-favorite --urn URN
```

**Output**
```json
{"status": "added",   "item_urn": "m2372861012"}
{"status": "removed", "item_urn": "m2372861012"}
```

---

### `me`

```
mrktplaats me
```

Get the authenticated user's profile (id, name, email, postcode, etc.).

---

### `my-ads`

```
mrktplaats my-ads [--status active|inactive|sold] [--limit N]
```

List own listings.

**Output**
```json
{
  "total": 1,
  "ads": [
    {
      "item_id": "m2373048475",
      "title": "Seiko SARV001 - SDK integratietest",
      "status": "active",
      "price_cents": 25000,
      "price_euros": 250.0
    }
  ]
}
```

---

### `delete-ad`

```
mrktplaats delete-ad --urn URN [--reason N]
```

Delete one of your own listings.

| Reason | Meaning |
|--------|---------|
| 1 | Sold on marktplaats.nl |
| 2 | Sold elsewhere |
| 3 | No longer selling |
| 4 | Other (default) |

**Output**
```json
{"status": "deleted", "item_urn": "m2373048475"}
```

---

## LLM agent usage guide

This tool is designed to be called by an LLM agent as a subprocess. All output
is structured JSON so the agent can parse results directly.

### Typical workflow for buying

```
1. search for items
   mrktplaats search --query "laptop" --size 10

2. inspect a promising listing
   mrktplaats listing --urn <urn from search>

3. check conversations to see if you already contacted this seller
   mrktplaats conversations

4. contact the seller (new conversation)
   mrktplaats send --urn <urn> --text "Is this still available? Can you do €X?"

   OR reply to an existing conversation
   mrktplaats reply --conv <conv_id> --text "..."

5. if the listing supports bidding (price_type = MIN_BID or FAST_BID)
   mrktplaats bid --urn <urn> --amount <euros>
   # to cancel: mrktplaats remove-bid --id <bid_id>

6. save interesting listings for later
   mrktplaats add-favorite --urn <urn>
```

### Typical workflow for selling

```
1. check own listings
   mrktplaats my-ads

2. delete a listing
   mrktplaats delete-ad --urn <urn>

3. check and reply to incoming messages
   mrktplaats conversations
   mrktplaats messages --conv <conv_id>
   mrktplaats reply --conv <conv_id> --text "..."
```

### Key fields to extract from responses

| Task | Command | Field to read |
|------|---------|---------------|
| Get listing URN from search | `search` | `listings[].urn` |
| Get seller ID (for bids/contact) | `listing` | `sellerInformation.id` |
| Get bid ID (to cancel) | `listing` or `bid` | `bids[].id` |
| Get conversation ID | `conversations` or `send` | `conversations[].id` / `conversation_id` |
| Check if bid-enabled | `listing` | `adCore.price.priceType == "MIN_BID"` |
| Get minimum bid | `listing` | `currentMinimumBid` (cents) |

### Error handling

On error the tool exits with code 1 and writes to stderr:

```
error: listing m9999: mrktplaats: API error 404: ...
```

The agent should check the exit code and parse the error message from stderr.
