# Polling

Polling lets you pull deliveries from a feed on your own schedule instead of receiving webhook pushes. It exists for platforms that cannot accept inbound HTTP requests, such as a firewalled system or one that can only fetch.

## Choosing a Delivery Method

Each partner uses one of two delivery methods:

- **Webhooks**: Tediware pushes each result to an endpoint as it is processed. This is the default and the recommended choice when your platform can receive HTTPS requests.
- **Polling**: Tediware builds no webhooks for the partner, and you pull deliveries from the feed instead.

Set the method on the **Delivery method** control on the partner **Configuration** card. New partners inherit a default you can set once under **Organization** settings (**Default delivery method for new partners**), so you don't have to choose it every time.

Choose **Polling** when you have no public HTTPS endpoint to receive pushes. Otherwise prefer **Webhooks**: a push arrives the moment a document is ready, with nothing for you to schedule.

Changing a partner's delivery method rebuilds its inbound and outbound flows. In **Polling** mode, none of the three webhooks (inbound, outbound notification, and error) are built, so any webhooks you had assigned stop firing for that partner.

## What the Feed Contains

The feed carries everything the three webhooks would have delivered:

- **Inbound** documents: a trading partner's EDI received and translated to JSON.
- **Outbound** confirmations: your JSON generated into EDI and delivered to the partner.
- **Errors**: a processing failure on either direction.

There is one entry per deliverable document, the same granularity at which webhooks fire. The feed is always populated, even for partners set to **Webhooks**, so a webhook partner can poll as well if that is useful for reconciliation. Entries follow the same retention as results and are removed after 45 days.

## Polling the Feed

Poll the feed with an authenticated request:

```
GET /platform/feed_entries
Authorization: Key <your-key>
```

Create a key under **API Keys** if you don't already have one. The feed returns only your organization's entries.

For a partner set to **Polling**, Tediware shows a **Poll for this partner's data** card on the partner page and a **Poll for this flow's data** card on each flow page. Each card has a ready-to-copy `curl` command with the partner key already filled in (and the direction on a flow page), plus a link to create an API key. It is the quickest way to get the exact call for a partner without assembling it by hand.

Narrow the feed with query parameters:

```
+-----------+-----------------------------------------------------------+
| Parameter | Meaning                                                   |
+-----------+-----------------------------------------------------------+
| partner   | Partner key to filter by (an unknown key returns empty)   |
| direction | inbound or outbound                                        |
| status    | success or error                                          |
| since     | ISO 8601 start point; entries created at or after it      |
| cursor    | High-water mark from a previous page (see below)          |
| limit     | Page size, default 50, capped at 100                      |
+-----------+-----------------------------------------------------------+
```

Each response carries a page of entries and a pagination block:

```
{
  "feedEntries": [
    {
      "id": "...",
      "direction": "inbound",
      "status": "success",
      "createdAt": "2026-06-08T12:00:00.000000Z",
      "partnerKey": "ACME",
      "traceGuid": "...",
      "resultId": "...",
      "detail": { ... }
    }
  ],
  "pagination": { "hasMore": false, "nextCursor": "..." }
}
```

The `detail` object is the result embedded inline, so each entry is self-sufficient: it carries the artifact ids and any error detail without a second lookup. To download the actual bytes of a document, use the `resultId` and its artifact ids against `GET /platform/artifacts/:id`.

## Cursors and the Safety Lag

Entries are returned oldest first. To page forward, keep the `nextCursor` from each response and pass it back as the `cursor` on your next request. When `hasMore` is `true`, request the next page immediately rather than waiting, until `hasMore` is `false`.

The newest entries are withheld for about 2 seconds before they appear in the feed. This safety lag guarantees that an advancing cursor can never skip an entry that committed slightly out of order, which makes polling lossless. A document you just processed may therefore not appear for a moment. This is expected and not a lost delivery.

A practical polling loop:

1. Request with your saved `cursor` (omit it on the first ever call, or use `since` to start from a point in time).
2. Process the returned entries and save `nextCursor`.
3. If `hasMore` is `true`, request the next page right away, until `hasMore` is `false`.
4. If `hasMore` is `false`, wait your chosen interval, then go to step 1.

Pick an idle interval that matches how quickly you need documents: anything from a few seconds to several minutes is reasonable, and most integrations are comfortable polling every minute or two. End-to-end latency is roughly your interval plus the 2-second safety lag. Polling faster than every couple of seconds gains nothing, since the safety lag withholds anything newer and those requests mostly return empty pages.

Errors are recorded per processing attempt, so a retried document can produce more than one error entry sharing the same `traceGuid`. Deduplicate errors on `traceGuid` if you only want one per document.

## Rate Limits

The feed shares the platform read budget of 240 requests per minute per API key, alongside result and artifact downloads. The usual way to exceed it is a burst of artifact downloads after draining a large page, not the polling itself. When you receive a `429`, wait for the `Retry-After` interval before retrying.
