Skip to content

Error Handling ​

All error responses use a uniform JSON format — regardless of the Accept header you send. The body never includes internal details like class names, paths, or a stack trace.

json
{
  "error": "error_code",
  "error_description": "Description of the error"
}

HTTP status codes ​

HTTP StatusError CodeDescription
400validation_errorInvalid request data (missing fields, wrong format)
400invalid_grantToken endpoint only: the owning user of the API access is disabled (error_description: The user of this client is disabled or deleted.)
400unauthorized_clientToken endpoint only: the API access was revoked, e.g. because its owning user was deleted (error_description: The grant type is unauthorized for this client_id)
400invalid_requestThe access token was not sent in the Authorization header, or was sent through more than one method at once
401invalid_clientInvalid or missing access token
401invalid_clientThe owning user of the API access is disabled (error_description: The user of this OAuth client is disabled or deleted.)
403client_revokedOAuth client has been revoked
403access_deniedThe endpoint is only available to Offisy Connect devices
403access_deniedThe owning user of your API access does not have the required permission (default error_description: Insufficient permission for this resource.)
403insufficient_scopeThe client does not have the required permission
404not_foundResource not found. On a single fetch of an invoice, fee note, cash revenue, or dunning notice that is not reachable for the owning user (tenancy without access), the response is exactly the same as for an unknown UUID: The requested resource was not found.
405method_not_allowedThe HTTP method isn't supported for this endpoint. The response includes an Allow header listing the allowed methods
429rate_limit_exceededToo many requests. Wait and try again.
other 4xxinvalid_requestCatch-all code for 4xx errors without their own code
5xxserver_errorInternal server error. The error_description doesn't include details

Note on query parameters: A query parameter passed as an array instead of a single value (?statusUuid[]=a&statusUuid[]=b) returns 400 validation_error on every OAuth endpoint. The same applies to invalid UTF-8. There is no generic HTML error page in these cases.

Note: For 429, the response includes a Retry-After header with the wait time in seconds. When the company budget is exceeded, the response body also includes plan and limit — this combination tells you whether the per-minute or the per-day limit applied. With the per-IP limit for invalid tokens, the body contains only error and error_description. See Rate Limits for details and the full limit table.

At the token endpoint (POST /api/oauth/v2/token), a 429 response also includes a reason field with the values company_minute, client_attempts, or ip.

Disabled or deleted owner ​

An API access works only as long as its owning user is active.

  • Disabled owner: The resource API responds with 401 invalid_client (The user of this OAuth client is disabled or deleted.), and the token endpoint with 400 invalid_grant. When the user is enabled again, the API access works again without further action.
  • Deleted owner: Deleting the user revokes their API accesses. After that, the resource API responds permanently with 403 client_revoked (OAuth client has been revoked.), even with an expired access token. The token endpoint responds with 400 unauthorized_client (The grant type is unauthorized for this client_id). A revoked API access cannot be restored.
  • Ended cooperation: An API access whose owner works at a cooperation partner loses its rights in the foreign company when the cooperation ends. The API accesses are not revoked, and requests respond with 403 access_denied.

Handling errors ​

JavaScript ​

javascript
const response = await fetch('https://my.offisy.at/api/oauth/v2/resource/appointments', {
  headers: { 'Authorization': 'Bearer {access_token}' }
});

// These three headers are present on every response, including successful ones
const limit = response.headers.get('X-RateLimit-Limit');
const remaining = response.headers.get('X-RateLimit-Remaining');
const reset = response.headers.get('X-RateLimit-Reset');

if (!response.ok) {
  const error = await response.json();
  console.error(`${error.error}: ${error.error_description}`);

  if (response.status === 429) {
    console.log(`Plan: ${error.plan}, Limit: ${error.limit}`);

    const retryAfter = response.headers.get('Retry-After');
    console.log(`Retry after ${retryAfter} seconds`);
  }
}

Offisy GmbH