GenieOSdocs

Errors

One envelope, typed codes, request ids that round-trip into the audit log.

Every error response from GenieOS uses the same envelope:

{
  "error": {
    "type": "validation_error",          // category — one of the values below
    "code": "schema_contract_violation", // specific code, scoped to type
    "message": "Variable `plan` must be one of [\"hobby\", \"pro\"], got \"founders\".",
    "fields": [                          // present on validation errors
      { "path": "variables.plan", "code": "enum_violation" }
    ],
    "retry_after_seconds": null,         // present on rate_limit_exceeded
    "request_id": "req_01JABC..."        // round-trips into the audit log
  }
}

The status code matches the type:

TypeStatusWhen it happens
authentication_error401Missing / malformed / revoked key
permission_denied403Key valid; scope insufficient
not_found404Resource doesn\u2019t exist (or is in another workspace)
validation_error422Body is wrong shape / fails the schema contract
conflict409Idempotency mismatch / version race
rate_limit_exceeded429Per-key or per-workspace; see Rate limits
internal_error500Bug on our side. Retry; we\u2019ll page
bad_gateway502Upstream connector hiccup. Retry
service_unavailable503Maintenance / capacity. Retry
gateway_timeout504Upstream connector timeout. Retry

The codes underneath are stable. We grow the code set with new values rather than renaming or repurposing existing ones, so your error-handling switch is forward-compatible.

Common codes

authentication_error

  • missing_authorization_header
  • invalid_api_key
  • revoked_api_key

permission_denied

  • scope_required
  • workspace_mismatch
  • feature_not_enabled

validation_error

  • body_invalid_json
  • field_required
  • field_type_mismatch
  • schema_contract_violation
  • idempotency_key_invalid
  • unknown_template

conflict

  • idempotency_key_mismatch
  • idempotency_key_in_progress
  • template_version_mismatch
  • webhook_signing_secret_in_use

rate_limit_exceeded

  • key_rate_limit
  • workspace_rate_limit
  • concurrent_writes_exceeded

SDK error classes

Both SDKs unwrap the envelope into typed exception classes — no string-matching on message.

import {
  GenieOS,
  GenieOSValidationError,
  GenieOSRateLimitError,
} from '@genie-os/sdk';

try {
  await mg.templates.send({ to, template: 'welcome', variables });
} catch (err) {
  if (err instanceof GenieOSValidationError) {
    for (const f of err.fields) console.warn(f.path, f.code);
    return;
  }
  if (err instanceof GenieOSRateLimitError) {
    await sleep(err.retryAfterSeconds * 1000);
    return retry();
  }
  throw err;
}
from genieos import (
    GenieOS,
    GenieOSValidationError,
    GenieOSRateLimitError,
)

try:
    mg.templates.send(to=to, template="welcome", variables=variables)
except GenieOSValidationError as e:
    for f in e.fields:
        print(f.path, f.code)
except GenieOSRateLimitError as e:
    sleep(e.retry_after_seconds)

The full class hierarchy:

  • GenieOSError — base.
    • GenieOSAuthError — 401 / 403.
    • GenieOSNotFoundError — 404.
    • GenieOSValidationError — 422 / 409.
    • GenieOSRateLimitError — 429.
    • GenieOSServerError — 5xx.
    • GenieOSNetworkError — DNS / TCP / TLS / timeout.

request_id

Every response — success or error — carries MailGenius-Request-Id as both a header and (on errors) inside the envelope. Quote it whenever you open a support ticket; it round-trips into the audit log so we can find the exact write you saw.

curl -i ... | grep -i mailgenius-request-id
# MailGenius-Request-Id: req_01JABCXYZQ9V...

What about field-level validation?

The fields[] array on validation_error contains one entry per offending input. Codes you\u2019ll see most often:

CodeMeaning
missingRequired field absent
type_mismatchWrong JSON type (e.g. string given for integer)
enum_violationValue not in the declared enum
format_invalidString didn\u2019t match a declared format (email, url, uuid)
length_out_of_rangeString / array length outside declared bounds
unknown_fieldField present but not declared in the schema (strict mode only)

This means your form code can do something better than "Something went wrong." See Schema contract for how the contract generates these codes.

Always log request_id

When something goes sideways at 3am, the difference between "my webhook delivered weird stuff" and "request req_01JABCXYZ... shows the upstream Stripe payload arrived malformed" is whether you logged the request id. Always log it.

On this page