Die Geschäfts-API-Dokumentation ist hinter einem Magic-Link geschützt. Öffne die ValueMatch-App, wähle in den Einstellungen „Geschäfts-API Doku öffnen" — du wirst direkt mit einem gültigen Token zurück hierher geleitet.
Voraussetzungen: KYC-Abschluss als „business" via Stripe Connect. Der Token ist 10 Minuten gültig.
Gewerbliche Verkäufer können Inserate über signierte HTTP-Aufrufe erstellen, aktualisieren und stornieren. Eine separate API gibt es nicht — das gleiche Event-Log, das die App nutzt, ist öffentlich erreichbar. Du signierst Events lokal mit deinem Wiederherstellungsschlüssel (BIP39-Mnemonic) und postest sie.
Basis-URL: https://api.valuematch.me
Um ein Inserat oder eine andere Aktion zu veröffentlichen, signierst
du eine SignedEvent-Hülle und postest sie an:
POST /events
Content-Type: application/json
X-API-Key: <App-API-Key>
Neue Inserate von noch nicht vertrauten Verkäufern erhalten
HTTP 202 Accepted mit einer pendingId — das
Inserat durchläuft eine Admin-Prüfung, bevor es auf der Kette landet.
Als „vertrauenswürdig“ markierte Verkäufer erhalten ein normales
200 OK mit dem Ledger-Eintrag.
Jeder Request-Body hat diese Form:
{
"type": "LISTING_CREATED",
"payload": { ... },
"actorPubkey": "<hex ed25519 public key, 64 Zeichen>",
"actorSignature": "<hex ed25519 signature, 128 Zeichen>",
"createdAt": 1748880000000,
"nonce": "<hex, 32 Zeichen>"
}
Die Signatur erfasst den SHA-256 des kanonischen JSON von
{ actorPubkey, createdAt, nonce, payload, type } —
Schlüssel alphabetisch sortiert, ohne Whitespace,
undefined-Werte weggelassen. Siehe das Node-Beispiel
unten als Referenzimplementierung.
| Feld | Typ | Pflicht | Hinweise |
|---|---|---|---|
auctionId | string | ja | von dir gewählte, eindeutige ID |
title | string | ja | Titel |
description | string | nein | max. 280 Zeichen |
tags | string[] | ja | mindestens eines |
startingPrice | number | ja | EUR, ganzzahlig |
reservePrice | number | ja | EUR, unter Startpreis |
decayAmount | number | ja | EUR pro decayUnit |
decayUnit | string | ja | minute / hour / day |
quantity | number | ja | aktuell muss 1 sein |
imageUrl | string | ja | vorher per POST /uploads hochladen |
shippingCost | number | nein | EUR; Pflicht wenn nicht nur Abholung |
localPickup | boolean | nein | Default false |
pickupLocation | string | nein | sichtbar wenn Abholung aktiv |
postalCode | string | nein | für Käufer sichtbarer Standort |
purchasedAt | number | nein | UTC ms-Zeitstempel; wird als Artikelalter angezeigt |
conditionKey | string | nein | einer von new, like_new, very_good, good, acceptable, for_parts |
sellerName | string | nein | Anzeigename für Käufer |
sellerType | string | ja | auf "business" setzen |
paymentIntentId | string | ja | Stripe PaymentIntent, der 10 % vom Startpreis blockiert |
Benötigt @noble/ed25519, @noble/hashes und
@scure/bip39. Installation:
npm i @noble/ed25519 @noble/hashes @scure/bip39
import * as ed from '@noble/ed25519';
import { sha256 } from '@noble/hashes/sha256';
import { mnemonicToSeedSync } from '@scure/bip39';
const BASE = 'https://api.valuematch.me';
const API_KEY = process.env.VALUEMATCH_API_KEY;
const MNEMONIC = process.env.VALUEMATCH_MNEMONIC; // 12-Wort Wiederherstellung
const toHex = (b) => Buffer.from(b).toString('hex');
const fromHex = (s) => Buffer.from(s, 'hex');
// Gleiches Ed25519-Keypair wie in der App ableiten
const seed = mnemonicToSeedSync(MNEMONIC).slice(0, 32);
const publicKey = await ed.getPublicKey(seed);
const PUBKEY_HEX = toHex(publicKey);
// Kanonisches JSON: Schlüssel sortiert, ohne Whitespace, undefined verwerfen
const canonical = (v) => {
if (v === null || typeof v !== 'object') return JSON.stringify(v);
if (Array.isArray(v)) return '[' + v.map(canonical).join(',') + ']';
const keys = Object.keys(v).filter((k) => v[k] !== undefined).sort();
return '{' + keys.map((k) => JSON.stringify(k) + ':' + canonical(v[k])).join(',') + '}';
};
async function signEvent(type, payload) {
const createdAt = Date.now();
const nonce = toHex(crypto.getRandomValues(new Uint8Array(16)));
const digest = sha256(new TextEncoder().encode(
canonical({ actorPubkey: PUBKEY_HEX, createdAt, nonce, payload, type })
));
const signature = await ed.sign(digest, seed);
return {
type, payload,
actorPubkey: PUBKEY_HEX,
actorSignature: toHex(signature),
createdAt, nonce,
};
}
async function createListing(payload) {
const envelope = await signEvent('LISTING_CREATED', payload);
const res = await fetch(BASE + '/events', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-API-Key': API_KEY },
body: JSON.stringify(envelope),
});
return { status: res.status, body: await res.json() };
}
Das Feld imageUrl muss auf eine URL zeigen, die das
Backend akzeptiert. Upload via:
POST /uploads
Content-Type: multipart/form-data
X-API-Key: <App-API-Key>
(file=<dein-bild>)
→ { "url": "...", "sha256": "...", "size": 12345, "contentType": "image/jpeg" }
Die gleiche Hüllen-Struktur gilt für:
LISTING_UPDATED — Inserat ändern, solange noch keine Gebote oder Limit-Aufträge bestehen.LISTING_CANCELLED — Inserat zurückziehen. Löst Rückerstattung oder Capture der Verkäufer-Kaution aus, je nach Engagement.SHIPMENT_MARKED — verkauften Artikel als versandt markieren (payload.trackingCode).Um deine in Prüfung befindlichen Einreichungen abzurufen, signiere eine GET-Anfrage:
GET /listings/mine/pending
X-API-Key: <App-API-Key>
X-Identity-Pubkey: <hex pubkey>
X-Identity-Timestamp: <unix ms>
X-Identity-Signature: <hex sig of sha256(canonical({ method, path, timestamp }))>
POST /events — 60 Requests/Minute.400 — Payload-Validierung fehlgeschlagen.403 — Signatur passt nicht zum actor-Pubkey.202 — Inserat zur Admin-Prüfung eingereiht.Standardmäßig durchläuft jedes neue Inserat eine manuelle Prüfung. Wenn du regelmäßig in Volumen einstellst und möchtest, dass Inserate sofort live gehen, melde dich bei up@pries.me von der E-Mail-Adresse, die in deinem Geschäftsprofil hinterlegt ist — wir nehmen deinen Pubkey dann in die Allow-List auf.