> ## Documentation Index
> Fetch the complete documentation index at: https://docs.heylua.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# New Relic

> Send agent logs to the New Relic Log API in its detailed form, with the batch resource in common and every record as one log

A `newrelic` drain posts to the New Relic Log API for your region. Each batch becomes one payload in the API's **detailed form**: the agent's identity once in `common.attributes`, and every record as one entry in `logs[]` with the platform's `lua.*` keys and the record's own attributes beside them.

<Info>
  Vendor destinations are opened per deployment. Where `newrelic` is not open yet, a create answers `422 DRAIN_TYPE_UNAVAILABLE` and the message names the types that are. **It may not be enabled on your deployment yet.**
</Info>

**Before you begin**

* A New Relic **licence key** (an ingest key). Not a user key and not an API key.
* The region your New Relic account is in.

## What to enter

| Field       | Flag               | Value                                       |
| ----------- | ------------------ | ------------------------------------------- |
| Name        | `--name`           | 1–64 characters, unique in the organization |
| Type        | `--type newrelic`  |                                             |
| Site        | `--site`           | One of the three below                      |
| Licence key | `--header Api-Key` | The CLI prompts for the value               |

`--endpoint` is **not** accepted for this type. The intake URL is derived from the site, so no drain configuration can point a batch carrying your licence key at a host of anyone's choosing:

| `--site`                  | Intake                                   |
| ------------------------- | ---------------------------------------- |
| `log-api.newrelic.com`    | `https://log-api.newrelic.com/log/v1`    |
| `log-api.eu.newrelic.com` | `https://log-api.eu.newrelic.com/log/v1` |
| `log-api.jp.newrelic.com` | `https://log-api.jp.newrelic.com/log/v1` |

Only those three hosts are allowed. Anything else is refused when the drain is created.

<Warning>
  **The header is `Api-Key`, not `Authorization`.** New Relic's Log API authenticates on its own header name, and a licence key sent as `Authorization` is simply not seen. This is the most common New Relic setup mistake.
</Warning>

```bash theme={null}
lua drains create \
  --name "New Relic prod" \
  --type newrelic \
  --site log-api.eu.newrelic.com \
  --header Api-Key \
  --environments production \
  --min-severity info
```

In CI:

```bash theme={null}
lua drains create --ci --json \
  --name "New Relic prod" --type newrelic \
  --site log-api.eu.newrelic.com \
  --header-from-env Api-Key=NEW_RELIC_LICENCE_KEY
```

## How ownership verification works

<Warning>
  **A vendor preset is reachability-checked, not ownership-verified.** `lua drains verify` posts one batch holding a single `lua.drain.test` record, and any `2xx` from the Log API is accepted. It proves the key works for that region. It does **not** prove you own the account. Only a [generic HTTPS](/drains/generic-https) drain is ownership-verified by a token echo, and only an [object-storage](/drains/object-storage) drain proves ownership by writing into a bucket.
</Warning>

```bash theme={null}
lua drains verify drn_0e94b6a2137fc85d4a11dd60
```

New Relic answers a successful post with **`202`**. A `403` means the licence key is wrong or belongs to another region — a US key against `log-api.eu.newrelic.com` fails exactly this way. Verification is limited to 5 attempts per drain per hour.

New Relic deliveries are **not signed**.

## What arrives

```json theme={null}
[
  {
    "common": {
      "attributes": {
        "service.name": "support-agent",
        "service.namespace": "org_4f2c9a1b",
        "service.instance.id": "agent_1789214224176_2vta8rnyn",
        "deployment.environment.name": "production",
        "lua.org.id": "org_4f2c9a1b",
        "lua.agent.id": "agent_1789214224176_2vta8rnyn"
      }
    },
    "logs": [
      {
        "timestamp": 1789217997644,
        "message": "Ticket lookup failed: upstream timeout",
        "attributes": {
          "lua.record.id": "1789217997644-jtoulxxxq",
          "lua.eventName": "lua.skill.error",
          "lua.severityText": "ERROR",
          "lua.severityNumber": 17,
          "lua.timestamp": "2026-09-12T12:59:57.644Z",
          "lua.observedTimestamp": "2026-09-12T12:59:59.102Z",
          "exception.message": "upstream timeout",
          "exception.type": "Error",
          "gen_ai.tool.name": "lookup_tickets",
          "lua.primitive.name": "tickets",
          "lua.source": "skill"
        }
      }
    ]
  }
]
```

| New Relic field     | Comes from                                                                                                                                                                 |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `common.attributes` | The batch's whole `resource` block, **once**                                                                                                                               |
| `logs[].timestamp`  | The record's `timestamp`, as epoch **milliseconds**. Omitted when it does not parse, so New Relic stamps ingest time rather than filing the event in 1970                  |
| `logs[].message`    | The record's `body`, or its `eventName` when there is no body                                                                                                              |
| `logs[].attributes` | `lua.record.id`, `lua.eventName`, `lua.severityText`, `lua.severityNumber`, `lua.timestamp`, `lua.observedTimestamp`, then the record's own attributes in sorted key order |

The detailed form is used because a batch already shares one identity — the flush groups by agent, environment, and agent version — so the resource belongs in `common` exactly once instead of repeated on all 500 logs. That is the difference between fitting the request and not.

### Useful queries

```sql theme={null}
SELECT * FROM Log WHERE `lua.eventName` = 'lua.skill.error' SINCE 1 hour ago

SELECT count(*) FROM Log FACET `lua.attributes.lua.source` SINCE 1 day ago

SELECT count(*) FROM Log WHERE `lua.eventName` = 'lua.drain.heartbeat' TIMESERIES
```

### Caps

| Cap                            | Value                              |
| ------------------------------ | ---------------------------------- |
| Records per batch              | 500                                |
| Uncompressed bytes per batch   | 750,000 bytes                      |
| Attributes per log             | 255                                |
| Characters per attribute value | 4,094                              |
| Compression                    | `gzip`, always                     |
| Retried                        | `408`, `429`, and every `5xx`      |
| Terminal                       | Every `3xx`, and every other `4xx` |

<Note>
  **New Relic's megabyte is decimal.** Its request limit is 1,000,000 bytes — 10⁶, not 2²⁰. Reading it as a mebibyte would put every full batch 4.9% over the limit, which is why the batch cap is 750,000 bytes rather than 768 KiB like the other presets.
</Note>

### Two truncations the encoder applies first

New Relic enforces both of these itself, silently. Lua applies them **before sending**, so what you see in New Relic matches what the drain's delivery row says it sent.

* **4,094 characters per attribute value**, and per `message`. A longer value is cut to that length. **No marker is appended** — the value simply ends.
* **255 attributes per log.** Beyond it New Relic drops attributes and does not say which. Lua keeps the platform's own `lua.*` keys first and fills the remainder from the record's attributes in **sorted key order**, so the outcome is deterministic and a retry re-encodes identically. A record with more than 255 attributes loses the alphabetically-last ones.

<Warning>
  **New Relic may drop an event whose timestamp is more than 48 hours old.** Lua only retries a batch for 6 hours, so a record normally reaches the intake far inside that window. It becomes reachable if a drain is paused for days and then resumed with a deep backlog: the oldest records in that backlog can be accepted with a `202` and then silently discarded. Nothing Lua can observe distinguishes that from a successful ingest — check New Relic itself after resuming a long-paused drain.
</Warning>

## Test the key with curl

```bash theme={null}
SITE='log-api.eu.newrelic.com'
NR_KEY='<your New Relic licence key>'

curl -sS -i -X POST "https://$SITE/log/v1" \
  -H "Api-Key: $NR_KEY" \
  -H 'Content-Type: application/json' \
  -d '[
    {
      "common": { "attributes": { "service.name": "support-agent", "lua.org.id": "org_4f2c9a1b" } },
      "logs": [
        {
          "timestamp": 1789217997644,
          "message": "Lua drain connectivity test",
          "attributes": { "lua.record.id": "probe-0001", "lua.eventName": "lua.drain.test", "lua.severityText": "INFO" }
        }
      ]
    }
  ]'
```

`202 {"requestId":"…"}` means accepted. `403` means the key is wrong for that region. Then:

```bash theme={null}
lua drains test drn_0e94b6a2137fc85d4a11dd60
```

## If it isn't working

<AccordionGroup>
  <Accordion title="403 on every delivery">
    Either the header is not `Api-Key`, or the licence key belongs to a different region. Keys are per-account and per-region: a key from a US account is refused by `log-api.eu.newrelic.com`. Recreate the drain with the right `--site`, or update the value with `lua drains update <id> --header Api-Key`.
  </Accordion>

  <Accordion title="202 but nothing appears in New Relic">
    A `202` means accepted for processing, not indexed. Check the timestamp first: New Relic may discard an event older than 48 hours, which is what a resumed long-paused drain produces.
  </Accordion>

  <Accordion title="Some attributes are missing on busy records">
    The 255-attribute ceiling. The platform's `lua.*` keys are kept first; the record's own attributes fill the remainder in sorted key order, so what is lost is alphabetically last. Narrow `--sources`, or use a destination without the ceiling — [object storage](/drains/object-storage) keeps every attribute.
  </Accordion>

  <Accordion title="Long messages end abruptly with no ellipsis">
    That is the 4,094-character cut, applied with no marker because New Relic itself appends none. The full body is still in `lua logs` and in any other drain on the same records.
  </Accordion>
</AccordionGroup>

## Next steps

<Columns cols={2}>
  <Card title="Event schema" href="/drains/event-schema">What every `lua.*` attribute means.</Card>
  <Card title="Protecting your destination" href="/drains/protecting-your-destination">What verification does and does not prove.</Card>
  <Card title="Delivery guarantees" href="/drains/delivery-guarantees">Retry, drops, heartbeat, and health states.</Card>
  <Card title="lua drains" href="/reference/cli/drains">Every verb and flag.</Card>
</Columns>
