VIES SOAP Fault Strings: MS_MAX_CONCURRENT_REQ Explained

6 Nov 2026

VIES SOAP Fault Strings: MS_MAX_CONCURRENT_REQ Explained

When the VIES checkVatService SOAP endpoint cannot answer, it replies with a SOAP fault, and the <faultstring> holds a bare code such as MS_MAX_CONCURRENT_REQ. None of these codes is a verdict on the VAT number you sent. We sort them into three groups: your input was rejected, VIES or one member state is overloaded or down, or something has been blocked. Only the middle group is worth retrying.

The fault strings at a glance

The first six rows are the faultstrings listed in the production WSDL. The last three appear only in the test-service WSDL as simulated outcomes, with no published meaning. For those, the meaning column is our reading of the name.

faultstringWhat it meansRetry?vatnode code and HTTP
INVALID_INPUTCountry code invalid or VAT number emptyNoINVALID_FORMAT 400
GLOBAL_MAX_CONCURRENT_REQConcurrent-request limit reached across the whole serviceYes, with backoffVIES_UNAVAILABLE 503
MS_MAX_CONCURRENT_REQConcurrent-request limit reached towards that member stateYes, with backoffVIES_UNAVAILABLE 503
SERVICE_UNAVAILABLEFailure in the network or application layer of the serviceYes, with backoffVIES_UNAVAILABLE 503
MS_UNAVAILABLEMember state system not answering or offlineYes, with backoffVIES_UNAVAILABLE 503
TIMEOUTTimed out waiting for a replyYes, with backoffUPSTREAM_TIMEOUT 504
INVALID_REQUESTER_INFOReading of the name: requester country or VAT number rejectedNoINVALID_REQUESTER 422
VAT_BLOCKEDReading of the name: this VAT number is blockedNo, stop and alertVIES_ERROR 403
IP_BLOCKEDReading of the name: the calling IP is blockedNo, stop and alertVIES_ERROR 403

The WSDL also defines input patterns: countryCode is two uppercase letters and vatNumber is 2 to 12 characters from a limited set. The WSDL ties INVALID_INPUT to an invalid country code or an empty VAT number; it does not say what a request breaking the patterns gets back.

Anatomy of a VIES SOAP fault

In our test call on 16 Oct 2026, with countryCode set to XX, the endpoint returned this:

<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"><env:Header/><env:Body><env:Fault><faultcode>env:Server</faultcode><faultstring>INVALID_INPUT</faultstring></env:Fault></env:Body></env:Envelope>

Two things stood out, and both are observations from that call, not documented guarantees.

The response came back with HTTP 200. Code that decides success or failure from the HTTP status alone would have treated this as a good answer and then failed later while parsing a missing valid field.

The faultcode was env:Server even though the problem was our input. So faultcode does not separate ‘you sent something wrong’ from ‘the server had a problem’. Branch on faultstring; it is the only reliable signal.

Input faults: INVALID_INPUT and INVALID_REQUESTER_INFO

INVALID_INPUT means the country code is not valid or the VAT number is empty. Retrying the same bytes returns the same fault. Fix the input, or validate the format before you call VIES.

INVALID_REQUESTER_INFO appears only in the test-service WSDL, with no published meaning. By its name it means the requester country or VAT number you supplied was rejected. Production VIES has also been seen answering a non-EU requester country with a human-readable faultstring, Invalid Requester member state, instead of a code. That is observed behaviour, not documented. If you match on exact codes, that one slips through, so match on the idea ‘requester-related’ as well.

One case that is not a fault: a number VIES reports as not valid, for example one that is unregistered or not active for cross-border supplies. VIES returns a normal reply with valid set to false. That is a completed check. A reply with a <faultstring> means the check did not complete.

MS_MAX_CONCURRENT_REQ and GLOBAL_MAX_CONCURRENT_REQ

These are the faults to expect when you send a burst of checks, such as a nightly revalidation job.

Per the WSDL, GLOBAL_MAX_CONCURRENT_REQ means the maximum number of concurrent requests has been reached, which it puts down to high traffic on the web application, and it says to resubmit later. MS_MAX_CONCURRENT_REQ is the same idea scoped to one member state: the maximum for that country has been reached, due to high traffic towards it.

The documentation describes a concurrency limit being reached, towards one member state or overall. It does not say the limit is per caller, per IP address or per key, and we have found no official source that does. So do not read it as ‘I am being rate limited’, and do not build logic that assumes your own traffic caused it. It may have been, and it may not.

Either way, the fix is the same. Reduce how many requests you have in flight at once, and retry the rejected ones later. If one country keeps returning MS_MAX_CONCURRENT_REQ while others are fine, queue that country separately with a lower concurrency cap so it does not starve the rest of your batch.

MS_UNAVAILABLE and SERVICE_UNAVAILABLE

The two look alike, and the difference is where the problem sits.

SERVICE_UNAVAILABLE is described as a failure in the network or application layer of the service. That points at VIES itself rather than at one country, although the WSDL does not say how many countries are affected.

MS_UNAVAILABLE means the member state’s own system is not answering or is offline. The problem is in that country, not in VIES itself, which is why one country can fail while checks for others succeed. The WSDL refers readers to the VIES Technical Information page for more.

Both say to try later. To check ahead of time, the Commission publishes a status endpoint, GET https://ec.europa.eu/taxation_customs/vies/rest-api/check-status, with per-member-state availability (Available, Unavailable or Monitoring Disabled). Treat it as a monitoring flag from the REST side of VIES, not a promise about your SOAP call. A state marked Available can still fail a check. For how often this happens in practice, see VIES uptime, measured, and for planning around it, the VIES downtime guide.

TIMEOUT

TIMEOUT means VIES got no reply within its allotted time. Like the others it says try later. It is a statement about the round trip, not about the number. A slow member state and a valid VAT ID are entirely compatible.

Your own client also needs a timeout, and its expiry is a separate event from the TIMEOUT fault. Both end the same way for your code: no answer yet. Handle them as one case.

VAT_BLOCKED and IP_BLOCKED

VAT_BLOCKED, IP_BLOCKED and the GLOBAL_MAX_CONCURRENT_REQ_TIME and MS_MAX_CONCURRENT_REQ_TIME variants show up only in the test-service WSDL, as simulated outcomes. The Commission has not published what they mean. Reading the names, VAT_BLOCKED points at the VAT number and IP_BLOCKED at the address you call from. We will not guess beyond that, and the _TIME variants have no published meaning either.

Handle a block conservatively. Do not hammer the endpoint with retries; they are unlikely to help and may prolong a block. Stop, log the full fault, and alert someone to look at it.

A retry policy you can copy

If you call VIES directly, this generic sketch (not vatnode’s code) sorts a faultstring into a class and retries only what is worth retrying. If you would rather not maintain it, the vatnode API returns these faults already mapped, and the free plan covers 100 checks a month.

type FaultClass = 'input' | 'transient' | 'blocked'

const INPUT = new Set(['INVALID_INPUT', 'INVALID_REQUESTER_INFO'])
const TRANSIENT = new Set([
  'GLOBAL_MAX_CONCURRENT_REQ',
  'MS_MAX_CONCURRENT_REQ',
  'SERVICE_UNAVAILABLE',
  'MS_UNAVAILABLE',
  'TIMEOUT',
])
const BLOCKED = new Set(['VAT_BLOCKED', 'IP_BLOCKED'])

function classify(faultstring: string): FaultClass {
  const code = faultstring.trim()
  if (INPUT.has(code) || /requester/i.test(code)) return 'input'
  if (BLOCKED.has(code)) return 'blocked'
  if (!TRANSIENT.has(code)) {
    console.warn('unknown VIES faultstring', code)
  }
  return 'transient' // unknown codes are retried within the attempt cap, but logged
}

class ViesFault extends Error {
  constructor(readonly faultstring: string) {
    super(faultstring)
  }
}

const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))

async function checkWithRetry<T>(
  call: () => Promise<T>, // throws ViesFault on a SOAP fault; map your own client timeout (AbortError) to new ViesFault('TIMEOUT')
  maxAttempts = 4,
  baseMs = 1000,
  capMs = 30_000
): Promise<T> {
  for (let attempt = 1; ; attempt++) {
    try {
      return await call()
    } catch (err) {
      if (!(err instanceof ViesFault)) throw err
      const kind = classify(err.faultstring)
      if (kind !== 'transient' || attempt >= maxAttempts) throw err
      const ceiling = Math.min(capMs, baseMs * 2 ** (attempt - 1))
      await sleep(ceiling / 2 + Math.random() * (ceiling / 2)) // equal jitter: never retries instantly
    }
  }
}

Run checkWithRetry behind a concurrency limiter, ideally one per country. Otherwise many parallel retry loops recreate the load that triggered MS_MAX_CONCURRENT_REQ in the first place. The fault itself says nothing about when to try again, so the backoff schedule is yours to choose.

What the caller does with a thrown ViesFault matters as much as the loop. Mark the number as ‘not checked’ and put it back in a queue for later. Never write it down as invalid. For blocked, stop the whole job and raise an alert instead of moving on to the next number. Our error handling guide covers the same ground from the vatnode API side, and caching VIES responses covers what is safe to store.

What vatnode does with each fault

If you call vatnode instead of VIES directly, the faults arrive already mapped. The error envelope carries the raw code in viesCode, and the mapping is in the table above. Here is what that looks like for MS_MAX_CONCURRENT_REQ (the requestId is a placeholder):

{
  "error": {
    "code": "VIES_UNAVAILABLE",
    "message": "Member State has reached maximum concurrent requests",
    "viesCode": "MS_MAX_CONCURRENT_REQ",
    "requestId": "req_abc123"
  }
}

The specifics, as they work today:

  • INVALID_INPUT becomes INVALID_FORMAT (400). If the account has a requester VAT set, it becomes INVALID_REQUESTER (422) instead.
  • INVALID_REQUESTER_INFO, and any fault text that mentions the requester (such as Invalid Requester member state), becomes INVALID_REQUESTER (422).
  • SERVICE_UNAVAILABLE, MS_UNAVAILABLE, MS_MAX_CONCURRENT_REQ and GLOBAL_MAX_CONCURRENT_REQ become VIES_UNAVAILABLE (503).
  • TIMEOUT, whether VIES sent it or our own 12-second SOAP budget ran out, becomes UPSTREAM_TIMEOUT (504).
  • VAT_BLOCKED and IP_BLOCKED become VIES_ERROR (403).
  • vatnode recognises those nine strings. Anything else, the _TIME variants included, gets no code of its own. Reading the code: a fault delivered with HTTP 200 is treated as an unparseable reply and, if the one REST retry also fails, comes back as VIES_ERROR (502) with viesCode set to PARSE_ERROR; one delivered with any other status is handled like SERVICE_UNAVAILABLE.

One REST retry. For SERVICE_UNAVAILABLE, network errors and unparseable responses, vatnode retries once through the Commission’s separate VIES REST gateway, with a 6-second budget. It does not do that for MS_UNAVAILABLE, because the same member-state system sits behind both gateways (our assumption; it is not documented), and not after TIMEOUT, because that would double the worst-case wait. If the REST retry fails too, the original SOAP error is what you get.

National fallback. For MS_UNAVAILABLE, MS_MAX_CONCURRENT_REQ, GLOBAL_MAX_CONCURRENT_REQ, SERVICE_UNAVAILABLE and TIMEOUT, vatnode can ask the national tax authority or company register instead, but only when no requester VAT is set on the account and a register adapter exists for that country (see coverage). That answer has source set to the register rather than VIES, and consultationNumber is null. A valid result from a national register is not a VIES confirmation. Without a consultation number it is not the same evidence for your records, and a register’s idea of registered may not match VIES validity for cross-border supplies. If the fallback fails as well, you get the original VIES error.

Requester VAT set. If you configured a requester VAT once in the dashboard’s Account details, there is no national fallback and no cache read, because only VIES issues a consultation number. The error comes straight back.

Error cache. The transient failures (TIMEOUT, SERVICE_UNAVAILABLE, MS_UNAVAILABLE, MS_MAX_CONCURRENT_REQ, GLOBAL_MAX_CONCURRENT_REQ) are cached for a given number for 60 seconds, except for requester accounts. A tight retry loop on one number therefore gets the cached error back quickly instead of causing another VIES call. Input faults and blocks are not cached.

Quota. Error responses are not counted against your monthly quota. A national-fallback answer is a normal check and is counted.

None of this guarantees an answer while VIES is down. It narrows the cases where you get nothing, and tells you which kind of nothing it was. The full list of vatnode codes is in the error reference, and the VIES API page covers how vatnode wraps VIES as a whole.

Try a single number in the checker, then create a free API key (100 checks a month, €0) and read viesCode from the response when something goes wrong.

FAQ

What does MS_MAX_CONCURRENT_REQ mean in VIES?

It means the maximum number of concurrent requests towards that member state has been reached. The WSDL describes it as high traffic towards the member state and tells you to resubmit later. No official source says the limit is per caller, per IP or per key, so treat it as congestion towards that member state, not as a verdict on your number. Retry with backoff.

What is the difference between MS_UNAVAILABLE and SERVICE_UNAVAILABLE?

SERVICE_UNAVAILABLE is a failure in the network or application layer of the VIES service itself. MS_UNAVAILABLE means the system of one member state is not answering or is offline, so the problem sits behind VIES, in one country. Both say try later, and both are transient.

Should I retry a VIES SOAP fault?

Only some of them. Retry the transient group (the two concurrent-request faults, SERVICE_UNAVAILABLE, MS_UNAVAILABLE and TIMEOUT) with exponential backoff, jitter and a capped number of attempts. Do not retry INVALID_INPUT, because the input will not change. Stop and alert on blocks. Never store any fault as an invalid result.

Does a VIES TIMEOUT mean the VAT number is invalid?

No. TIMEOUT means no reply came within the allotted time, so the check did not complete. An invalid number comes back as a normal reply with valid set to false, not as a fault. Requeue the number and record it as not checked.