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.
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 identifierdetail.AdCore.Title/.Description— listing textdetail.AdCore.Price.PriceType—"FIXED","MIN_BID","FAST_BID","FREE","ON_REQUEST"detail.AdCore.Price.PriceAmount— price in euro centsdetail.AdCore.Pictures— slice ofPicturewith URLs at various sizesdetail.SellerInformation.ID— seller's user ID (needed forRelevant.Get)detail.Bids— current bidsdetail.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:
- Resizes the image (starting at 1200 px max dimension, stepping down by 25% each iteration)
- Re-encodes as JPEG at quality 80
- 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.