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

> Read records from an S2 stream by sequence number, timestamp, or tail offset.

# Read records.

<CardGroup cols={2}>
  <Card title="Read concepts" icon="list-timeline" href="/concepts/reads">
    Understand starting points, bounds, wait behavior, and sessions.
  </Card>

  <Card title="Reading SDK" icon="code" href="/sdk/reading">
    Use single-batch reads, read sessions, and check-tail calls.
  </Card>
</CardGroup>

Use this endpoint for a single-batch read or a streaming read session.

Streaming sessions may be [S2S](/api/protocol#sessions)-based (used by SDK [`readSession`](/sdk/reading#read-session) APIs) or [SSE responses](#sse).

## Parameters

Query parameters choose [where the read starts](#starting-point), [what bounds apply](#bounds), and [whether the read waits at the tail](#waiting-at-the-tail).

### Starting point

A request can specify at most one starting selector. If no selector is provided, the start is equivalent to `tail_offset=0`.

| Query parameter | Unit                          | Starts at                                                                        |
| --------------- | ----------------------------- | -------------------------------------------------------------------------------- |
| `seq_num`       | Sequence number               | The first record whose sequence number is greater than or equal to `seq_num`.    |
| `timestamp`     | Milliseconds since Unix epoch | The first record whose timestamp is greater than or equal to `timestamp`.        |
| `tail_offset`   | Record count                  | The position `tail.seq_num - tail_offset`, saturated at the start of the stream. |

`tail_offset=0` starts at the tail. The tail is the next sequence number that will be assigned, so there is no existing record at that position.

If a requested `seq_num` or `timestamp` is beyond the current tail, `clamp=true` moves the start to the current tail. This is most useful with `wait > 0`, where the read can then wait for future records. Without `clamp`, starts beyond the tail return `416 Range Not Satisfiable`.

### Bounds

Bounds limit how far a read may continue after its starting point.

| Query parameter | Unit      | Behavior                                                                                        |
| --------------- | --------- | ----------------------------------------------------------------------------------------------- |
| `count`         | Records   | Stop after this many records. For single-batch reads, the effective maximum is 1000 records.    |
| `bytes`         | Bytes     | Limit the total metered bytes returned. For single-batch reads, the effective maximum is 1 MiB. |
| `until`         | Timestamp | Stop before records whose timestamp is greater than or equal to `until`.                        |

When multiple bounds are present, the read stops at the first bound reached.

### Waiting at the tail

`wait` is how many seconds the read may wait at the tail for new records after all immediately available records have been returned.

| Request mode                                 | Default `wait` | Tail behavior                                                                                       |
| -------------------------------------------- | -------------- | --------------------------------------------------------------------------------------------------- |
| Single-batch read                            | `0`            | Return immediately unless `wait > 0`.                                                               |
| Session with `count`, `bytes`, or `until`    | `0`            | Stop when a bound is reached or when the session catches up to the current tail, unless `wait > 0`. |
| Session without `count`, `bytes`, or `until` | Infinite       | Follow the stream indefinitely, waiting at the tail for new records.                                |

A single-batch read with `wait > 0` may long poll for up to 60 seconds. If the wait elapses before records arrive, the response is an empty batch.

For sessions, `wait` applies throughout the session while still respecting any `count`, `bytes`, or `until` bound.

## When reads return 416

A read returns `416 Range Not Satisfiable` when it cannot return records under the request's effective `wait` and `clamp` behavior. The [`416` response body](/api/error-codes#read-range-not-satisfiable-416) is a `TailResponse` containing the current tail.

| Request behavior     | `416` when                                                                                                                              |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| Effective `wait > 0` | The start is beyond the tail and `clamp` is omitted or `false`: `seq_num > tail.seq_num` or `timestamp > tail.timestamp`.               |
| Effective `wait = 0` | No record is available immediately: the stream is empty, `tail_offset = 0`, `seq_num >= tail.seq_num`, or `timestamp > tail.timestamp`. |

**Starting exactly at the tail is valid**; with `wait > 0`, the read waits for future records, and with `wait = 0`, it returns `416` because no record exists at the tail yet.

<Note>
  `clamp=true` only changes starts that are beyond the tail. It moves the start to the current tail, not to the last existing record.
</Note>

## SSE

Server push with [Server-Sent-Events](https://html.spec.whatwg.org/multipage/server-sent-events.html) (SSE) is supported. Clients can request an SSE response by setting the `Accept: text/event-stream` header.

<CodeGroup>
  ```bash Request theme={null}
  curl --request GET \
    --url 'https://hello-world.b.s2.dev/v1/streams/test/records?seq_num=0' \
    --header "Authorization: Bearer ${S2_ACCESS_TOKEN}" \
    --header 'Accept: text/event-stream'
  ```
</CodeGroup>

<CodeGroup>
  ```bash Batch theme={null}
  event: batch
  id: 4,5,74
  data: {"records":[{"seq_num":4,"timestamp":1750101582144,"body":"hello"}],"tail":{"seq_num":5,"timestamp":1750101582144}}
  ```

  ```bash Ping theme={null}
  event: ping
  data: {"timestamp":1750101582144}
  ```

  ```bash Error theme={null}
  event: error
  data: Something went wrong
  ```

  ```bash Done theme={null}
  data: [DONE]
  ```
</CodeGroup>

Any `error` message will terminate the stream.


## OpenAPI

````yaml get /streams/{stream}/records
openapi: 3.1.0
info:
  title: S2, the durable streams API
  description: Streams as a cloud storage primitive.
  termsOfService: https://s2.dev/terms
  contact:
    email: support@s2.dev
  license:
    name: MIT
  version: 1.0.0
servers:
  - url: https://aws.s2.dev/v1
security:
  - access_token: []
tags:
  - name: metrics
    description: Usage metrics and data.
  - name: basins
    description: Manage basins
  - name: access-tokens
    description: Manage access tokens
  - name: locations
    description: Manage locations
  - name: streams
    description: Manage streams
  - name: records
    description: Manage records
paths:
  /streams/{stream}/records:
    servers:
      - url: https://{basin}.b.s2.dev/v1
        description: Endpoint for the basin
        variables:
          basin:
            default: ''
            description: Basin name
    get:
      tags:
        - records
      summary: Read records.
      operationId: read
      parameters:
        - name: stream
          in: path
          description: Stream name.
          required: true
          schema:
            $ref: '#/components/schemas/StreamNameStr'
        - name: s2-format
          in: header
          description: >-
            Defines the interpretation of record data (header name, header
            value, and body) with the JSON content type.

            Use `raw` (default) for efficient transmission and storage of
            Unicode data — storage will be in UTF-8.

            Use `base64` for safe transmission with efficient storage of binary
            data.
          required: false
          schema:
            $ref: '#/components/schemas/Format'
        - name: s2-encryption-key
          in: header
          description: |-
            Encryption key material for append and read operations.
            Provide base64-encoded key when stream encryption is enabled.
          required: false
          schema:
            type: string
        - name: seq_num
          in: query
          description: Start from a sequence number.
          required: false
          schema:
            $ref: '#/components/schemas/u64'
        - name: timestamp
          in: query
          description: Start from a timestamp.
          required: false
          schema:
            $ref: '#/components/schemas/u64'
        - name: tail_offset
          in: query
          description: Start from number of records before the next sequence number.
          required: false
          schema:
            type: integer
            format: int64
            minimum: 0
        - name: clamp
          in: query
          description: |-
            Start reading from the tail if the requested position is beyond it.
            Otherwise, a `416 Range Not Satisfiable` response is returned.
          required: false
          schema:
            type: boolean
        - name: count
          in: query
          description: |-
            Record count limit.
            Non-streaming reads are capped by the default limit of 1000 records.
          required: false
          schema:
            type: integer
            format: int64
            minimum: 0
        - name: bytes
          in: query
          description: |-
            Metered bytes limit.
            Non-streaming reads are capped by the default limit of 1 MiB.
          required: false
          schema:
            type: integer
            minimum: 0
        - name: until
          in: query
          description: Exclusive timestamp to read until.
          required: false
          schema:
            $ref: '#/components/schemas/u64'
        - name: wait
          in: query
          description: >-
            Duration in seconds to wait for new records.

            The default duration is 0 if there is a bound on `count`, `bytes`,
            or `until`, and otherwise infinite.

            Non-streaming reads are always bounded on `count` and `bytes`, so
            you can achieve long poll semantics by specifying a non-zero
            duration up to 60 seconds.

            In the context of an SSE or S2S streaming read, the duration will
            bound how much time can elapse between records throughout the
            lifetime of the session.
          required: false
          schema:
            type: integer
            format: int32
            minimum: 0
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReadBatch'
            text/event-stream:
              schema:
                $ref: '#/components/schemas/ReadEvent'
        '400':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorInfo'
        '403':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorInfo'
        '404':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorInfo'
        '408':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorInfo'
        '409':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorInfo'
        '416':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TailResponse'
components:
  schemas:
    StreamNameStr:
      type: string
      maxLength: 512
      minLength: 1
    Format:
      type: string
      enum:
        - raw
        - base64
    u64:
      type: integer
      format: int64
      minimum: 0
    ReadBatch:
      type: object
      required:
        - records
      properties:
        records:
          type: array
          items:
            $ref: '#/components/schemas/SequencedRecord'
          description: >-
            Records that are durably sequenced on the stream, retrieved based on
            the requested criteria.

            This can only be empty in response to a unary read (i.e. not SSE),
            if the request cannot be satisfied without violating an explicit
            bound (`count`, `bytes`, or `until`).
        tail:
          oneOf:
            - type: 'null'
            - $ref: '#/components/schemas/StreamPosition'
              description: >-
                Sequence number that will be assigned to the next record on the
                stream, and timestamp of the last record.

                This will only be present when reading recent records.
    ReadEvent:
      oneOf:
        - type: object
          title: batch
          required:
            - event
            - data
            - id
          properties:
            data:
              $ref: '#/components/schemas/ReadBatch'
            event:
              type: string
              enum:
                - batch
            id:
              type: string
              pattern: ^[0-9]+,[0-9]+,[0-9]+$
        - type: object
          title: error
          required:
            - event
            - data
          properties:
            data:
              type: string
            event:
              type: string
              enum:
                - error
        - type: object
          title: ping
          required:
            - event
            - data
          properties:
            data:
              $ref: '#/components/schemas/PingEventData'
            event:
              type: string
              enum:
                - ping
    ErrorInfo:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: string
        message:
          type: string
    TailResponse:
      type: object
      required:
        - tail
      properties:
        tail:
          $ref: '#/components/schemas/StreamPosition'
          description: >-
            Sequence number that will be assigned to the next record on the
            stream, and timestamp of the last record.
    SequencedRecord:
      type: object
      description: Record that is durably sequenced on a stream.
      required:
        - seq_num
        - timestamp
      properties:
        body:
          type: string
          description: Body of the record.
        headers:
          type: array
          items:
            $ref: '#/components/schemas/Header'
          description: Series of name-value pairs for this record.
        seq_num:
          $ref: '#/components/schemas/u64'
          description: Sequence number assigned by the service.
        timestamp:
          $ref: '#/components/schemas/u64'
          description: Timestamp for this record.
    StreamPosition:
      type: object
      description: Position of a record in a stream.
      required:
        - seq_num
        - timestamp
      properties:
        seq_num:
          $ref: '#/components/schemas/u64'
          description: Sequence number assigned by the service.
        timestamp:
          $ref: '#/components/schemas/u64'
          description: >-
            Timestamp, which may be client-specified or assigned by the service.

            If it is assigned by the service, it will represent milliseconds
            since Unix epoch.
    PingEventData:
      type: object
      required:
        - timestamp
      properties:
        timestamp:
          type: integer
          format: int64
          minimum: 0
    Header:
      type: array
      items:
        type: string
      maxItems: 2
      minItems: 2
  securitySchemes:
    access_token:
      type: http
      scheme: bearer
      description: >-
        Bearer authentication header of the form `Bearer <token>`, where
        `<token>` is your access token.

````