More API than the docs show: using unofficial weclapp endpoints safely
You want to reconcile 400 customer records against a list of customer numbers. With GET, you pack all 400 numbers into one filter, and suddenly the URL is longer than what proxy and server accept. The usual solution is to split the list into small packages and send many requests. There is a more elegant way: with an endpoint that the weclapp docs recommend in the text but that is missing from the public path list.
It's not the only one. The public spec is the foundation of every integration, but it isn't the whole API. In this article we show you how to find out what your tenant can do in addition, which of these endpoints brings the most in everyday work and by which rules you use it, so that your integration doesn't hang on something that can change without warning.
All examples use the base URL https://<dein-mandant>.weclapp.com/webapp/api/v2/ and the header AuthenticationToken: <dein-token>. Try them first on a test system, never directly on your production tenant.
More API than the path list shows
The public v2 spec on weclapp.com lists 712 paths. If you instead fetch the spec from a tenant, with the parameter includeHidden=true, there are significantly more: on our two test tenants it was 1,013 and 1,020 paths. Around 300 paths that appear in no public list.
What does that mean for you? These endpoints work, but they have no contract. Even for the official API, weclapp reserves the right in its change policy to change resources without advance notice. For endpoints that aren't even in the catalog, you should expect it all the more. On top of that: a tenant shows only the endpoints of its license. What is missing for you may exist for a customer with a different module scope, and vice versa.
For you, this means: unofficial endpoints are not taboo, but every use is a deliberate decision with a built-in plan B.

How to find out what your tenant can do
You download your tenant's spec directly from your tenant endpoint. The path meta/openapi.yaml is named by the weclapp docs themselves, the parameter includeHidden=true delivers the full version. With two downloads and a few lines of Python you see which operations your tenant additionally knows:
curl -s --compressed -H "AuthenticationToken: <dein-token>" \
"https://<dein-mandant>.weclapp.com/webapp/api/v2/meta/openapi.yaml?includeHidden=true" -o tenant.yaml
curl -s --compressed https://www.weclapp.com/api/openapi_v2.yaml -o public.yaml
pip install pyyaml
python3 - <<'EOF'
import yaml
def ops(f):
spec = yaml.safe_load(open(f))
return {(m.upper(), p) for p, item in spec["paths"].items()
for m, o in item.items() if isinstance(o, dict) and "responses" in o}
for method, path in sorted(ops("tenant.yaml") - ops("public.yaml")):
print(method, path)
EOF
The list that comes out is more than a curiosity. Let the script run regularly, for example once a week, and compare the result with the last run. If an endpoint you use disappears, you see it in the diff and not only when a customer calls.
For you, this means: in five minutes you have an inventory and an early warning system in one.
The most useful find: filter in the body with POST /query
The largest block in this list is formed by two operations that exist for every entity: POST /{entity}/query and POST /{entity}/count, 183 times each in our spec. The interesting part: they are no secret. In the section “Querying”, the weclapp docs explicitly describe POST /party/query and recommend filter expressions in the request body for POST queries, because POST has fewer restrictions than GET. They just don't appear in the path list of the public spec.
That solves the problem from the beginning. The long list moves from the URL into the body:
POST https://<dein-mandant>.weclapp.com/webapp/api/v2/party/query
AuthenticationToken: <dein-token>
Content-Type: application/json
{
"filter": "customerNumber in [\"10004\", \"10005\", \"10006\"]",
"pageSize": 1000
}
The response has the same shape as for GET /party, an object with result. If you only need the count, use POST /party/count with the same filter.
You should know two differences from GET. First, the body has no individual filters like customerNumber-in, only the field filter with a filter expression. The docs still label filter expressions as beta. Second, some fields are named differently: according to the schema, besides filter, page, pageSize and offset there are also properties, includeReferencedEntities, additionalProperties and orderBy, each as a list of strings. As with GET: weclapp generally doesn't deliver more than 1,000 entries per page.
For you, this means: reconciliations against long ID or number lists need one request instead of many, and your URLs stay short and readable.

How to use unofficial endpoints safely
An endpoint without a contract is unproblematic when your code keeps running without it. For POST /query that's easy, because the official way exists: GET with id-in, in packages so the URL stays short. The code first tries the POST, and only when weclapp responds with 404 or 405 does it switch over and report that:
// unofficial: POST /party/query, weclapp can change it without announcement.
// Fallback: GET /party with id-in in packages of 100 IDs.
async function queryParties(ids) {
const filter = `id in [${ids.map((id) => `"${id}"`).join(",")}]`;
try {
return await post("/party/query", { filter, pageSize: 1000 });
} catch (err) {
if (![404, 405].includes(err.status)) throw err;
console.warn("POST /party/query nicht verfügbar, Fallback auf GET");
const result = [];
for (let i = 0; i < ids.length; i += 100) {
const chunk = ids.slice(i, i + 100);
const page = await get(`/party?id-in=${encodeURIComponent(JSON.stringify(chunk))}&pageSize=100`);
result.push(...page.result);
}
return { result };
}
}
The function is designed for up to 1,000 IDs. If you have more, split the list beforehand.
A few rules have proven themselves from our integration day-to-day. Every unofficial endpoint in the code gets a comment like the one above, so that colleagues a year from now also know what they're relying on. The weekly spec diff shows you whether the endpoints are still there. Reading endpoints like /query are a manageable risk. Writing unofficial endpoints belong in an integration only with preview, approval and subsequent verification, and never in an order flow that stands still without them. And because every tenant shows only the endpoints of its license, when rolling out to a new customer you check whether the endpoint exists there at all.
For you, this means: you get the convenience of the additional endpoints without your integration depending on them.

What else is in the list
Your spec diff will show more than /query. Among them are reading item entities like salesOrderItem, a change history via auditing/history, project and task management, a key-value store (appSettings) and asynchronous bulk operations for changing and deleting. The last two in particular write to your tenant, and for bulk deletions the spec knows no dry run. You test such endpoints exclusively on a test system and with the rules from above.
Checklist
- Fetched your tenant's spec with
includeHidden=trueand compared it with the public one - Commented the unofficial endpoints in use in the code
- Official fallback present for each unofficial endpoint
- Spec diff runs regularly
- Writing unofficial calls only with preview, approval and verification
- Checked availability when rolling out to a new customer
Keep reading and tools
The quickest way to try the official endpoints is our free Postman Collection 4 weclapp. You add the unofficial requests from this article there as your own requests, and set tenant and token once as variables. How you read large data volumes selectively and in packages is shown in part 2 of the series (reading selectively). The YouTube webinar “weclapp API Deep Dive” explains the basics of filters and projection.
Want to know how robust your integrations are against changes to the weclapp API? Let's take a look: wals.pro/termin.