Create a transport rule

POST/api/v1/transport-rules
Requires anorganization API keywith the scopetransport_rules:write

Request

POST/api/v1/transport-rules
curl -X POST 'https://app.mailyte.com/api/v1/transport-rules' \
  -H 'Authorization: Bearer mk_live_YOUR_API_KEY'
The key names its own organization, so no X-Organization-ID header is needed.

Response

Success.

  • dataobject
    • objectstringtransport_rule
    • idstring

      ULID, issued by the mail server. These arrive UPPERCASE (`01M2RV5CEM4MT97RQ00ANRRFCP`) while every Laravel-issued id in the product is lowercase. Both are valid Crockford base32; compare them exactly rather than case-insensitively, because we do not normalise an id we have already issued. NULL MEANS UNKNOWN, never "this rule has none": the mail server's `TransportRuleResponse` declares this field REQUIRED and FastAPI validates the response against that model, so a 2xx body cannot omit it. It is published nullable because the shape is defined in another repository and we read it defensively rather than assume it -- a null means we did not get a readable rule back, not that we got a rule without one. Do not substitute a default; treat the rule as unreadable and report it.

    • namestring

      The rule's display name, required on create. NULL MEANS UNKNOWN, never "this rule has none": the mail server's `TransportRuleResponse` declares this field REQUIRED and FastAPI validates the response against that model, so a 2xx body cannot omit it. It is published nullable because the shape is defined in another repository and we read it defensively rather than assume it -- a null means we did not get a readable rule back, not that we got a rule without one. Do not substitute a default; treat the rule as unreadable and report it.

    • descriptionstring

      Free text about the rule. NULL MEANS NONE -- the rule genuinely has no description -- and this is the ONE field on a transport rule where that is what null means. Upstream declares it `description: str | None`, the only optional field on the response model, and `null` round-trips through create unchanged. Everything else nullable on this object is a null we could not read; see `id`.

    • directionstringinbound | outbound | both

      Which mail the rule is evaluated against. Defaults to `both` on create, so null is never "unset". NULL MEANS UNKNOWN, never "this rule has none": the mail server's `TransportRuleResponse` declares this field REQUIRED and FastAPI validates the response against that model, so a 2xx body cannot omit it. It is published nullable because the shape is defined in another repository and we read it defensively rather than assume it -- a null means we did not get a readable rule back, not that we got a rule without one. Do not substitute a default; treat the rule as unreadable and report it.

    • enabledboolean

      A disabled rule keeps its priority and is not evaluated.

    • priorityinteger

      LOWER RUNS FIRST. `PUT /transport-rules/reorder` rewrites these in steps of 10 so a rule can be inserted between two others without renumbering the set. Defaults to 100 on create, so null is never "no priority" and must not be sorted as 0, which would run the rule FIRST. NULL MEANS UNKNOWN, never "this rule has none": the mail server's `TransportRuleResponse` declares this field REQUIRED and FastAPI validates the response against that model, so a 2xx body cannot omit it. It is published nullable because the shape is defined in another repository and we read it defensively rather than assume it -- a null means we did not get a readable rule back, not that we got a rule without one. Do not substitute a default; treat the rule as unreadable and report it.

    • matchobject

      How a message is matched. Was two top-level fields, `condition_logic` and `conditions`, with the prefix doing the grouping; grouped so you can hand the whole matcher to whatever evaluates it.

      • logicstringall | any

        `all` requires every condition, `any` requires one. Defaults to `all` on create. NULL MEANS UNKNOWN, never "this rule has none": the mail server's `TransportRuleResponse` declares this field REQUIRED and FastAPI validates the response against that model, so a 2xx body cannot omit it. It is published nullable because the shape is defined in another repository and we read it defensively rather than assume it -- a null means we did not get a readable rule back, not that we got a rule without one. Do not substitute a default; treat the rule as unreadable and report it. In particular do NOT read a null as `any`: that would widen a rule from "every condition" to "any condition" and apply its actions to mail it was never meant to touch.

      • conditionsarray<object>

        Key order is fixed at `field`, `operator`, `value`. Upstream returns `{field, value, operator}` on read and accepts `{field, operator, value}` on write, so the same rule changed shape round-tripping through this API without changing meaning.

        • fieldstringsender | recipient | subject | header | size | has_attachment

          What is examined. `size` is the message size in bytes; `has_attachment` is a boolean test. NULL MEANS UNKNOWN -- the stored condition carried no such key -- and never that it is unset, empty or "match anything". Unlike the rest of this object, this value is NOT validated on the way out: the mail server types `conditions` as a bare `list[dict]` on its response model, so the `transport_rules.conditions` JSON column is published exactly as stored. A rule created through this API cannot produce it (our request rules and upstream's own model both require the key), but a row written any other way is forwarded unexamined. Treat the condition as uninterpretable rather than guessing at it.

        • operatorstringequals | contains | starts_with | ends_with | regex | greater_than | less_than

          How `value` is compared against `field`. NULL MEANS UNKNOWN -- the stored condition carried no such key -- and never that it is unset, empty or "match anything". Unlike the rest of this object, this value is NOT validated on the way out: the mail server types `conditions` as a bare `list[dict]` on its response model, so the `transport_rules.conditions` JSON column is published exactly as stored. A rule created through this API cannot produce it (our request rules and upstream's own model both require the key), but a row written any other way is forwarded unexamined. Treat the condition as uninterpretable rather than guessing at it.

        • valuestring

          What to compare against. ALWAYS A STRING, including for the numeric operators: a `size greater_than` condition reads back as `"1048576"`, quoted, because both our request rules (`conditions.*.value` => `required|string`) and upstream's `TransportRuleCondition.value: str` type it that way, and the column stores what they accepted. Parse it yourself for `greater_than` / `less_than`. (Published untyped until phase 3; a caller reading the old description would have expected a number here and got a numeric string.) NULL MEANS UNKNOWN -- the stored condition carried no such key -- and never that it is unset, empty or "match anything". Unlike the rest of this object, this value is NOT validated on the way out: the mail server types `conditions` as a bare `list[dict]` on its response model, so the `transport_rules.conditions` JSON column is published exactly as stored. A rule created through this API cannot produce it (our request rules and upstream's own model both require the key), but a row written any other way is forwarded unexamined. Treat the condition as uninterpretable rather than guessing at it.

    • actionsarray<object>

      What happens to a matched message, in order.

      • typestringadd_header | modify_subject | redirect | bcc | reject | add_disclaimer | quarantine

        What is done to a matched message. NULL MEANS UNKNOWN -- the stored action carried no such key -- and never that it is unset, empty or "match anything". Unlike the rest of this object, this value is NOT validated on the way out: the mail server types `actions` as a bare `list[dict]` on its response model, so the `transport_rules.actions` JSON column is published exactly as stored. A rule created through this API cannot produce it (our request rules and upstream's own model both require the key), but a row written any other way is forwarded unexamined. Treat the action as uninterpretable rather than guessing at it. A null is NOT "do nothing": the enforcer reads the same column, so an action you cannot name is one you cannot rule out either.

      • paramsobject

        The action's parameters, keyed by name. ALWAYS an object: it was a PHP array, so it serialised as `[]` when empty and `{}` otherwise, and an action with no parameters must not be a different JSON type from one with some.

    • created_atstring

      ISO 8601 UTC. Upstream sends `"2026-09-17T23:27:53"` -- MySQL NOW() through Python's datetime.isoformat(), with no offset and no Z, so a caller could not tell the timezone; it is UTC and is re-emitted as such. NULL MEANS UNKNOWN, never that the rule has no such instant -- every rule was created and every rule was last written. Upstream declares the field REQUIRED (`created_at: str`) and then substitutes an EMPTY STRING when the column is missing, which is neither a timestamp nor a null and would parse as "now" if taken at face value; that empty string is what becomes null here. So a null says the mail server had no value to give us, and the rule is older or stranger than its timestamps suggest -- do not order or age rules on a null.

    • updated_atstring

      ISO 8601 UTC. Upstream sends `"2026-09-17T23:27:53"` -- MySQL NOW() through Python's datetime.isoformat(), with no offset and no Z, so a caller could not tell the timezone; it is UTC and is re-emitted as such. NULL MEANS UNKNOWN, never that the rule has no such instant -- every rule was created and every rule was last written. Upstream declares the field REQUIRED (`created_at: str`) and then substitutes an EMPTY STRING when the column is missing, which is neither a timestamp nor a null and would parse as "now" if taken at face value; that empty string is what becomes null here. So a null says the mail server had no value to give us, and the rule is older or stranger than its timestamps suggest -- do not order or age rules on a null.

Returned inside the standard envelope.

Errors

StatusWhen
401The API key is missing, unknown, revoked or expired. All four answer identically, on purpose: distinguishing them would confirm which keys exist.
403The key is valid but may not do this: it lacks the required scope, its IP allowlist does not include you, or this endpoint does not accept API keys.
404No such resource in this organization.
422The request was understood but the values were not acceptable.
429Too many requests, or the organization has spent its sending allowance. `Retry-After` says how long to wait.

Every status, with what causes it and what to do, is on the error reference.