End Close Relays API

The Relays API from End Close — 3 operation(s) for relays.

Operations 3

GET /relays/config Get relay configuration #
POST /relays/events Accept a legacy relay check-in #
PUT /relays/instances/{instance_id} Report a relay instance's manifest #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/end-close:end-close-relays-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

end-close-relays-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: End Close Relays API
  description: REST API is used to interact with the End Close platform.
  license:
    name: MIT
  version: 1.0.0
servers:
- url: https://api.endclose.com/v1
security:
- ApiKeyAuth: []
tags:
- name: Relays
paths:
  /relays/config:
    get:
      summary: Get relay configuration
      description: 'The routes document End Close is managing for the environment this API key belongs to: which processor webhook routes the relay accepts, how each is verified, and the field map that decides what leaves your network.


        This is the only thing End Close tells a relay, and the status code is the whole message. A 200 is itself the statement that End Close owns this configuration, so the relay runs the document and hides its own config editor — there is no local copy that can drift. A 304 says the same and that nothing moved. A 404 means End Close is not managing this environment (nothing provisioned, or the switch turned off in Settings → Relay). It is a state, not a terminal answer: keep the document you already have, fall back to local configuration if you have one, and keep polling at your normal cadence — this is the only way you will hear that management was switched back on, and stopping forwarding on a 404 would turn a settings toggle into an outage.


        Poll it with `If-None-Match`. The 200 carries a strong `ETag` identifying the document, which changes only when the document itself changes — a save that alters nothing keeps it — so the ordinary answer on a heartbeat is a 304 with no body. The API key alone resolves the environment: no parameters are needed, and rotating the key does not change the document. `routes` has the same content as the `routes:` block of the relay''s `relay.yaml`; `environment` is informational.'
      parameters:
      - in: header
        name: If-None-Match
        required: false
        schema:
          type: string
        description: The ETag from the last 200. Send it back and an unchanged document answers 304.
      responses:
        '200':
          description: The provisioned routes document. The ETag header identifies it.
          headers:
            ETag:
              description: 'Strong validator for this document. Opaque to the relay: store it and send it back as If-None-Match.'
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RelayConfig'
        '304':
          description: Not Modified — End Close still owns this configuration and the document has not changed. No body; keep running what you have.
        '401':
          description: Unauthorized — missing, invalid, or revoked API key
        '403':
          description: Forbidden — the key is a run-scoped agent session key, not a relay key
        '404':
          description: End Close is not managing the relay configuration for this environment. Keep the document you have and keep polling — a 404 is a state, not a terminal answer.
      tags:
      - Relays
      operationId: getRelaysConfig
      x-operation-id-source: derived
  /relays/events:
    post:
      summary: Accept a legacy relay check-in
      description: 'Accepted for legacy relay clients (v0.11.0–v0.12.4) that still call home with `{ "name", "properties" }` (`relay_boot`, `relay_heartbeat`, `relay_error`, `relay_shutdown`, `relay_killswitch`, `relay_config_applied`, `relay_batch_parked`, or whatever an older build sends). The body is logged and discarded: nothing is stored, and this is not a configuration channel. It does not return `{ config: { managed, fingerprint } }`. Current relays report themselves with PUT /relays/instances/{instance_id} and read configuration from GET /relays/config. A 204 with an empty body means the event was accepted. Same API-key auth as the other /relays endpoints.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: Whatever the legacy client sends. `name` and `properties` are logged; other keys are logged too. Names are not validated.
              properties:
                name:
                  type: string
                  example: relay_heartbeat
                properties:
                  type: object
                  additionalProperties: true
                  description: Event fields the client allowlists (version, config YAML, queue, routes, stack, …). Logged in full. `config` may be the relay's YAML; `stack` is treated as sensitive.
              additionalProperties: true
      responses:
        '204':
          description: Accepted and logged. Empty body. Not a configuration response.
        '400':
          description: Body is not JSON, or exceeds 64 KB
        '401':
          description: Unauthorized — missing, invalid, or revoked API key
        '403':
          description: Forbidden — the key is a run-scoped agent session key, not a relay key
      tags:
      - Relays
      operationId: postRelaysEvents
      x-operation-id-source: derived
  /relays/instances/{instance_id}:
    put:
      summary: Report a relay instance's manifest
      description: 'A running relay instance describing itself: host kind, engine version, the routes-document schema version it understands, where its configuration comes from, and the adapters and enrichments it has registered. Idempotent replace keyed by the environment the API key belongs to plus a host-chosen instance id (up to 128 characters; embedded hosts without a configured id use a random UUID per process). Send it on boot and on every heartbeat. End Close checks any document saved for the environment against the instances seen in the last 24 hours: every route `source` must be an adapter every live instance has, every `enrich:` an enrichment every live instance has registered (none at all while any live instance is the standard application), and no document feature newer than the oldest instance''s `config_schema`. Instances unseen for 30 days are deleted. Nothing is required of a relay that never sends a manifest: authoring then falls back to the built-in adapters and no enrichments.'
      parameters:
      - in: path
        name: instance_id
        required: true
        schema:
          type: string
          maxLength: 128
        description: Host-chosen identifier for this running instance
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RelayInstanceManifest'
      responses:
        '204':
          description: Manifest stored
        '400':
          description: Malformed manifest; the message names the field
        '401':
          description: Unauthorized — missing, invalid, or revoked API key
        '403':
          description: Forbidden — the key is a run-scoped agent session key, not a relay key
        '413':
          description: Body exceeds 64 KB
      tags:
      - Relays
      operationId: putRelaysInstancesByInstanceId
      x-operation-id-source: derived
components:
  schemas:
    RelayRoute:
      type: object
      description: 'One webhook route on the relay. Validated on save against the relay''s own rules (schema in End-Close/endclose-relay, packages/core/src/config/schema.ts): route ids are unique lowercase slugs, `source` names a known adapter, `auth.secret_env` is the NAME of an environment variable on the relay (never a secret value), metadata output names are snake_case and may not be on the relay''s hard denylist (cvv, ssn, account/routing number, password, api key, ...) unless hashed, and `enrich:` references are not accepted.'
      required:
      - id
      - source
      - auth
      - map
      properties:
        id:
          type: string
          pattern: ^[a-z0-9][a-z0-9-_]*$
          description: Unique lowercase slug for the route.
          example: payabli-settlements
        source:
          type: string
          enum:
          - payabli
          - generic_hmac
          description: The processor adapter that verifies and parses the webhook.
        auth:
          type: object
          description: 'How inbound webhooks are verified. `mode: static_header` (header, secret_env, allowed_ips) or `mode: hmac` (header, algorithm, secret_env, signed_content, timestamp_header, tolerance_seconds, event_id, event_type). `secret_env` names an environment variable on the relay.'
          required:
          - mode
          - secret_env
          properties:
            mode:
              type: string
              enum:
              - static_header
              - hmac
            header:
              type: string
            secret_env:
              type: string
              pattern: ^[A-Z][A-Z0-9_]*$
              example: PAYABLI_WEBHOOK_SECRET
            allowed_ips:
              type: array
              items:
                type: string
        events:
          type: array
          minItems: 1
          items:
            type: string
          description: Payload event types accepted by this route (glob `*` allowed). Others are dropped locally and never forwarded.
        map:
          type: object
          description: 'How a webhook event becomes an End Close record: only fields named here are forwarded. Field references are dot-notation paths into the payload (`batchId`, `batch.id`) or `{ source, transform }` objects (transforms: trim, lowercase, hash).'
          required:
          - data_stream_key
          - external_id
          - amount
          - direction
          properties:
            data_stream_key:
              type: string
            external_id:
              description: Field reference
            amount:
              description: Field reference to a string amount like "3,762.87"; converted to integer cents.
            direction:
              type: string
              enum:
              - credit
              - debit
            date:
              description: 'Optional payload timestamp: a path, or `{ source, format }` with format `iso8601` (default) or `mdy_hms`. Absent means the record is dated by receive time.'
            description:
              description: Optional field reference
            currency:
              type: string
              pattern: ^[A-Za-z]{3}$
            metadata:
              type: object
              description: Extra fields to forward, as snake_case output name → field reference.
              additionalProperties: true
        max_body_bytes:
          type: integer
          minimum: 1
          maximum: 10485760
          default: 1048576
    RelayConfig:
      type: object
      required:
      - routes
      properties:
        environment:
          type: string
          description: 'Name of the environment the API key belongs to, and that owns this document. Informational: the relay logs it and writes it into a comment in its stored copy.'
          example: Sandbox
        routes:
          type: array
          minItems: 1
          description: 'The relay''s routes document — the same content as the `routes:` block of `relay.yaml`. Never empty: an environment with no document gets a 404 instead.'
          items:
            $ref: '#/components/schemas/RelayRoute'
    RelayInstanceManifest:
      type: object
      required:
      - schema
      - reason
      - host
      - engine_version
      - config_schema
      - config_source
      - capabilities
      properties:
        schema:
          type: integer
          enum:
          - 1
          description: Manifest format version. Unknown values are rejected with 400.
        reason:
          type: string
          example: heartbeat
          description: 'Why the relay is checking in. One lowercase word: `boot`, `heartbeat`, `config_applied`, `error` and `shutdown` are the ones End Close knows about, and any other lowercase word is accepted and stored, so a new relay version is never blocked from checking in. Only `shutdown` carries meaning: it is recorded but does not refresh the instance''s liveness, so an instance that says goodbye stops constraining what a document may name instead of counting as alive for another 24 hours. Send no other detail with it — extra keys are stored verbatim and never read, which is the wrong home for stack traces or metrics.'
        host:
          type: string
          enum:
          - application
          - embedded
          description: The shipped Docker image, or `createRelay()` embedded in the customer's own backend.
        engine_version:
          type: string
          description: Semver of `@end-close/relay` in use.
          example: 0.12.2
        config_schema:
          type: integer
          minimum: 1
          description: 'The routes-document schema version this engine understands. Version 1 is current. Gates which document features may be saved: nothing newer than the oldest reporting instance understands.'
        config_source:
          type: string
          enum:
          - remote
          - local
          description: '`remote` fetches from End Close; `local` has routes supplied in code or by file. Informational.'
        capabilities:
          type: object
          required:
          - adapters
          properties:
            adapters:
              type: array
              items:
                type: string
              description: Lowercase adapter slugs. Always includes the built-ins `payabli` and `generic_hmac`.
              example:
              - payabli
              - generic_hmac
              - stripe
            enrichments:
              type: object
              description: Registered enrichment functions keyed by lowercase snake_case name. Each value is a descriptor that may be empty; recognised keys are `description` (string) and `output` (`string` | `number` | `boolean`). Unknown keys are stored as sent.
              additionalProperties:
                type: object
                properties:
                  description:
                    type: string
                  output:
                    type: string
                    enum:
                    - string
                    - number
                    - boolean
                additionalProperties: true
              example:
                resident_name:
                  output: string
                  description: Resident full name from the Payabli payor id
                resident_unit: {}
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-KEY
x-provenance:
  method: harvested
  publisher: End Close
  source: https://www.endclose.com/docs/openapi.json
  note: split by refine-openapis from the provider's own published document, verified by fetch (openapi/_original/*.fetch.json);
    stamped by stamp-openapi-provenance.py (roadmap#793)