Files
Joren 25410749db Initial Marktplaats SDK scaffold
Clone the twdehands SDK into mrktplaats and retarget naming and defaults for app.marktplaats.nl while keeping the same request/response bodies and endpoint behavior.
2026-04-15 23:45:49 +02:00

12 KiB

mrktplaats

Go SDK for the marktplaats.nl mobile API.

Installation

go get github.com/joren/mrktplaats

Quick start

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/joren/mrktplaats"
)

func main() {
    ctx := context.Background()

    // Unauthenticated client (search, browse)
    anon := mrktplaats.NewClient()

    results, err := anon.Search.Fetch(ctx, &mrktplaats.SearchRequest{
        Query: "fiets", Page: 1, Size: 10, ShowListings: true,
    }, nil)
    if err != nil {
        log.Fatal(err)
    }
    for _, item := range results.Items {
        if l := item.Listing(); l != nil {
            fmt.Printf("%s — %s\n", l.AdCore.URN, l.AdCore.Title)
        }
    }

    // Authenticated client
    client := mrktplaats.NewClient()
    login, err := client.Auth.Login(ctx, "email@example.com", "password")
    if err != nil {
        log.Fatal(err)
    }
    // handle 2FA if needed (login.Verification != nil)
    _ = login

    // Or use a saved token directly
    authed := mrktplaats.NewClient(mrktplaats.WithAccessToken("your-token"))
    me, err := authed.Users.Me(ctx)
    fmt.Printf("Logged in as %s\n", me.Name)
}

Client options

Option Description
WithAccessToken(token) Set a pre-existing access token
WithHTTPClient(hc) Provide a custom *http.Client
WithBaseURL(url) Override the base URL (default: https://app.marktplaats.nl)
WithSession(id) Set the x-adevinta-session-id header
WithGAClientID(id) Set the x-adevinta-ga-client-id header

Services

Auth — client.Auth

Login(ctx, email, password) (*LoginResponse, error)

Authenticates with email + password. If 2FA is required the response has Verification != nil.

login, err := client.Auth.Login(ctx, "email", "pass")
if login.Verification != nil {
    // prompt for SMS code
    auth, err := client.Auth.VerifyCode(ctx, login.Verification.RequestID, "123456")
    client.SetAccessToken(auth.Auth.AccessToken)
}

VerifyCode(ctx, requestID, code) (*AuthResponse, error)

Completes SMS 2FA. AuthResponse.Auth.AccessToken is the bearer token.


Search — client.Search / anon.Search

Fetch(ctx, req, supportedTypes) (*SearchResponse, error)

Full listing search with filters.

resp, err := anon.Search.Fetch(ctx, &mrktplaats.SearchRequest{
    Query:                       "laptop",
    CategoryID:                  intPtr(377),   // optional
    Page:                        1,
    Size:                        20,
    SortBy:                      "SORT_INDEX",  // or "DATE_DESC", "PRICE_ASC", "PRICE_DESC"
    AllowCorrection:             true,
    SearchOnTitleAndDescription: true,
    ShowListings:                true,
    Languages:                   []string{"nl-BE"},
}, nil)
for _, item := range resp.Items {
    if l := item.Listing(); l != nil {
        fmt.Printf("%s  €%.2f  %s\n",
            l.AdCore.URN, float64(l.AdCore.Price.PriceAmount)/100, l.AdCore.Title)
    }
}

SearchItem.Listing() returns the listing or nil for non-listing items (ads, banners).

KeywordSuggestions(ctx, prefix, categoryID) (*KeywordSuggestionsResponse, error)

Auto-complete suggestions for search input.

DisplayTargeting(ctx, req) ([]DisplayTargetingResult, error)

Fetch ad targeting slots for the search results page.


Listings — client.Listings / anon.Listings

Get(ctx, urn) (*ListingDetail, error)

Fetch full listing details (title, description, pictures, seller, bids, attributes).

detail, err := client.Listings.Get(ctx, "m2372861012")
fmt.Printf("Seller: %s (ID %d)\n", detail.SellerInformation.Name, detail.SellerInformation.ID)
fmt.Printf("Min bid: €%.2f\n", float64(detail.CurrentMinimumBid)/100)

Key fields:

  • detail.AdCore.URN — listing identifier
  • detail.AdCore.Title / .Description — listing text
  • detail.AdCore.Price.PriceType — "FIXED", "MIN_BID", "FAST_BID", "FREE", "ON_REQUEST"
  • detail.AdCore.Price.PriceAmount — price in euro cents
  • detail.AdCore.Pictures — slice of Picture with URLs at various sizes
  • detail.SellerInformation.ID — seller's user ID (needed for Relevant.Get)
  • detail.Bids — current bids
  • detail.CurrentMinimumBid — minimum next bid in cents

DisplayTargeting(ctx, req) ([]DisplayTargetingResult, error)

Fetch ad targeting slots for the listing detail page.


Discovery — anon.Discovery

Feeds(ctx, desiredCount) (*FeedsResponse, error)

Returns homepage discovery feed metadata (feed IDs, titles).

FeedListings(ctx, feedID, opts) (*FeedListingsResponse, error)

Returns listings for a discovery feed (e.g. "f8" = "Voor jou").

resp, err := anon.Discovery.FeedListings(ctx, "f8", &mrktplaats.FeedListingsOptions{
    Size: 40, PageLocation: "HOMEPAGE_DISCOVERY",
})

Categories — anon.Categories

All(ctx) (*CategoriesResponse, error)

Returns the full category tree.

Buckets(ctx, categoryID) ([]CategoryBucket, error)

Returns sub-category buckets for a category ID.


Messaging — client.Messaging

Conversations(ctx, opts) (*ConversationsResponse, error)

Lists all conversations for the authenticated user.

resp, err := client.Messaging.Conversations(ctx, &mrktplaats.ConversationsOptions{Limit: 50})
for _, conv := range resp.Conversations {
    fmt.Printf("[%s] %q  with=%s  unread=%d\n",
        conv.ID, conv.Title, conv.OtherParticipant.Name, conv.UnreadMessagesCount)
}

conv.ItemID is the listing URN the conversation is about. conv.LatestMessage() returns the most recent *Message or nil.

Messages(ctx, conversationID, offset, limit) (*MessagesResponse, error)

Fetches messages in a conversation.

StartConversation(ctx, itemURN, text) (string, error)

Starts a new conversation about a listing. Returns the conversation ID.

SendMessage(ctx, conversationID, text) (string, error)

Sends a message in an existing conversation. Returns the message ID.

ServerTime(ctx) (time.Time, error) / EmailSubscriptions(ctx) (json.RawMessage, error)


Favorites — client.Favorites

List(ctx, limit) (*FavoritesResponse, error)

Lists saved/favourite listings. Each FavoriteItem has ItemID and CreationDate.

Add(ctx, urns...) ([]FavoriteItem, error)

Adds one or more listings to favourites.

Remove(ctx, urns...) error

Removes one or more listings from favourites.


Users — client.Users

Me(ctx) (*User, error)

Returns the authenticated user's profile (ID, name, email, postcode).

Reviews(ctx, userID, role) (*ReviewsResponse, error)

Returns reviews for a user.

resp, err := client.Users.Reviews(ctx, userID, mrktplaats.ReviewRoleReviewee)
fmt.Printf("%.1f / 5  (%d reviews)\n", resp.Summary.AverageScore, resp.Summary.NumberOfReviews)

role is one of ReviewRoleAll, ReviewRoleReviewee.


MyAccount — client.MyAccount

MyAds(ctx, opts) (*MyAdsResponse, error)

Lists the authenticated user's own listings.

resp, err := client.MyAccount.MyAds(ctx, &mrktplaats.MyAdsOptions{
    Status: "active",  // "active", "inactive", "sold"
    Limit:  50,
    Counts: true,
})

DeleteAds(ctx, itemIDs, reason) error

Removes listings. reason is one of:

  • DeleteReasonSoldOnTweedehands (1)
  • DeleteReasonSoldElsewhere (2)
  • DeleteReasonNotSelling (3)
  • DeleteReasonOther (4)

UpdateProfile(ctx, req) error

Updates name, phone number, postcode, or NDFC declaration status.


SYI (Sell Your Item) — client.SYI

Form(ctx, categoryID) (*SYIFormResponse, error)

Returns the listing creation form (attributes, supported values) for a category.

UploadImage(ctx, img, filename) (string, error)

Uploads an image and returns its pictureId. Large images are automatically compressed to stay within the API's 900 KB size limit.

f, _ := os.Open("photo.jpg")
defer f.Close()
pictureID, err := client.SYI.UploadImage(ctx, f, "photo.jpg")

Create(ctx, req) (*CreateAdResponse, error)

Publishes a new listing. Returns CreateAdResponse; use .URN() to get the new listing's ID.

resp, err := client.SYI.Create(ctx, &mrktplaats.CreateAdRequest{
    CategoryID:   1831, // Horloges | Heren
    Translations: []mrktplaats.Translation{{
        Locale:      "nl-BE",
        Title:       "Seiko SARV001",
        Description: "Automatisch horloge in goede staat.",
    }},
    PriceInCents:    25000,  // €250
    PriceType:       "FIXED",
    PictureIDs:      []string{pictureID},
    DeliveryMethod:  "Ophalen",
    ShippingConfig:  mrktplaats.ShippingConfig{Carriers: []string{}},
    SellerName:      "Jonathan",
    Postcode:        "8000",
    SelectedBundle:  "FREE",
    SYISessionID:    fmt.Sprintf("session-%d", time.Now().UnixMilli()),
    IntegratedShippingForTWHEnabled: true,
})
fmt.Println(resp.URN()) // "m2373048475"

PriceSuggestion(ctx, categoryID, title) (*PriceSuggestionResponse, error)

Returns price range suggestions (low/mid/high) based on similar listings.

AttributeSuggestions(ctx, categoryID, text) (json.RawMessage, error)

Suggests attribute values (brand, condition, etc.) from a description.

SuspiciousKeywordsWarnings(ctx, categoryID, text) (json.RawMessage, error)

Checks a description for policy-violating keywords.


Saleability — client.Saleability

Recognize(ctx, img, filename) (*RecognizeResponse, error)

Runs image recognition to predict the category and attributes for a photo. Large images are automatically compressed.

f, _ := os.Open("photo.jpg")
resp, err := client.Saleability.Recognize(ctx, f, "photo.jpg")
for _, p := range resp.Predictions {
    fmt.Printf("%s (%.0f%% confidence)\n", p.CategoryName, p.Confidence*100)
}

Enquiry (Bidding) — client.Enquiry

PlaceBid(ctx, itemURN, req) (*BidsResponse, error)

Places a bid. Value is in euro cents.

resp, err := client.Enquiry.PlaceBid(ctx, "m2372861012", &mrktplaats.PlaceBidRequest{
    Value:           2000,  // €20.00
    PersonalMessage: "Graag een bod van €20",
})
// resp.Bids contains all bids, resp.CurrentMinimumBid is the new minimum

RemoveBid(ctx, bidID) (*BidsResponse, error)

Removes a bid by its numeric ID.


Relevant — anon.Relevant

Get(ctx, listingURN, sellerID, categoryID) (*SimilarAdsResponse, error)

Returns similar/related listings. sellerID is required (use detail.SellerInformation.ID from Listings.Get).


Notifications — client.Notifications

UnreadCount(ctx) (*UnreadCountResponse, error)

Returns the number of unread notifications.


Payments — client.Payments

CartItemCount(ctx) (*PaymentCartCountResponse, error)

Returns the number of items in the payment cart.


Config — client.Config

Labs(ctx) (*LabsConfig, error)

Returns A/B and feature switch configuration.


Error handling

detail, err := client.Listings.Get(ctx, urn)
if err != nil {
    if mrktplaats.IsNotFound(err) {
        // 404 — listing doesn't exist
    }
    if mrktplaats.IsUnauthorized(err) {
        // 401 — token expired or invalid
    }
    // generic: err contains status code and response body
}

*APIError exposes StatusCode int and Body []byte for custom handling.


Image compression

Both SYI.UploadImage and Saleability.Recognize automatically compress images that exceed 900 KB before uploading. The compression:

  1. Resizes the image (starting at 1200 px max dimension, stepping down by 25% each iteration)
  2. Re-encodes as JPEG at quality 80
  3. Stops when the result is under 900 KB

Unsupported image formats are passed through unchanged.


Running integration tests

# First run — performs a full login with 2FA prompt
go test -v -tags integration -run TestIntegration -timeout 180s

# Subsequent runs — reuses the saved token from .test-token
go test -v -tags integration -run TestIntegration -timeout 180s

Tests cover all 16 service areas against the live API.