JENNi Events API
Look up the delivery status of a single previously submitted item.
Look up the delivery status of a single previously submitted item.
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
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.curl "https://apis.io/api/v1/apis/jennipro-com:jennipro-com-events-api"
curl "https://apis.io/api/v1/apis?limit=25"
Calls without X-API-Key need no key. Calls with it need a paid plan — get a 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: 3.2.0
info:
contact:
email: partners@jennipro.com
name: JENNi partner integrations
description: 'One API, read by four kinds of partner. **Get started** (left) is the same for everyone:
sign up, get a key, make five calls. Then pick who you are: each card names the guide to
read first and the API sections you will actually call, in that order. The sidebar is
grouped the same way; everything cross-cutting (errors, paging, limits, versioning) sits
under **Reference** at the end.
POS systems
You run the point of sale, inventory or product-data system for retailers, and you push their catalog and live stock into JENNi.
What you do: register each store, open a run, push products and stock levels (or a full snapshot), close the run, read back what landed.
Read, then call: POS: catalog & inventory → Stores · Runs · Catalog · Inventory
First call: POST /provider/v1/stores
Live in production.
Brands
You own the product content — titles, images, descriptions, GTINs — for products that retailers stock, and you want JENNi''s listings to carry your version.
What you do: send product records through the same ingestion lane a POS uses, then make sure each one clears the readiness bar, because a record that lands but is not ready is never sold.
Read, then call: Brands → Product readiness → Catalog (listed under POS systems)
First call: GET /provider/v1/products
Same lane as POS. Brand-to-retailer matching is handled by JENNi, not by an API call.
Marketplaces & channels
You are a demand channel — a marketplace, delivery app or storefront — that lists JENNi merchants'' stock and sends orders back.
What you do: read one store''s shelf at a time, poll or receive changes, and agree an order path with JENNi (orders in are a JENNi-built adapter, not a public endpoint).
Read, then call: Marketplace & channel → Channel · Events · Webhooks
First call: GET /channel/v1/stores
The read contract is settled and documented; it is enabled per channel — the guide says what is callable today.
Fulfillment & delivery
You pick, pack, ship or hand over orders for JENNi merchants.
What you do: today, plan. The fulfillment service has no partner-callable route; the guide documents what is built so you can scope against it.
Read: Fulfillment & delivery
First call: none yet — talk to your JENNi contact.
Planning only today; talk to your JENNi contact before building.
## Before your first call
- **Get a credential:** *Sign in / Sign up* (top right) → verify your email → *Request a
key*. JENNi approves it and you claim the secret once — Quickstart walks
through it.
- **Base URLs:** `https://prod.jennipro.net` is production; `https://dev.jennipro.app` is the
sandbox, with its own credentials issued on request.
- **Credential:** `X-Jenni-Api-Key: .` on every request — see
Authentication and Scopes. Your tenant and provider come
from the key; no tenant id ever travels in a path or body.
- **Errors** are RFC-7807 `application/problem+json`; branch on `type`, quote
`X-Request-Id` to support — see Errors.
- **Writes** carry `Idempotency-Key`; lists page with `cursor` + `limit` — see
Idempotency and Pagination.
Where a capability is not yet available to partners, the page says so in place.'
title: JENNi Catalog & Inventory Ingestion Events API
version: '2026-09-20'
servers:
- description: Production
url: https://prod.jennipro.net
- description: Development / integration sandbox
url: https://dev.jennipro.app
security:
- PartnerApiKey: []
tags:
- description: Look up the delivery status of a single previously submitted item.
name: Events
paths:
/provider/v1/events/{event_id}:
get:
description: 'Looks up what happened to a single item you submitted, by the `event_id` you gave it. Use it to chase an individual item without replaying a whole batch.
⚠️ **Only two of the four declared statuses are ever returned today.** An item that JENNi accepted reports `pending` and stays there permanently: nothing downstream currently advances it, so `published` and `consumed` are reserved for a future release and are never emitted. Read `pending` as "accepted by JENNi", NOT as "still being processed". A rejected item reports `failed` with the reason in `failure`.
An event belonging to another provider returns `404`, exactly like one that does not exist.
To review a whole run''s outcomes at once, use `GET /provider/v1/runs/{run_id}/items?status=rejected` instead of calling this per item.'
operationId: getEventStatus
parameters:
- in: path
name: event_id
required: true
schema:
minLength: 1
type: string
- description: Your own correlation id. It is echoed back on the response and stamped on the RFC-7807 problem body as `request_id`. Omit it and JENNi generates one.
in: header
name: X-Request-Id
required: false
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/EventStatusView'
description: The item's status.
headers:
X-Request-Id:
description: Correlation id for this request — echoed from X-Request-Id when you sent one, otherwise generated.
schema:
type: string
'400':
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
description: The event_id path parameter failed validation.
headers:
X-Request-Id:
description: Correlation id for this request — echoed from X-Request-Id when you sent one, otherwise generated.
schema:
type: string
'401':
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
description: Authentication failed — the X-Jenni-Api-Key header is missing, malformed, or names a key that is unknown or revoked.
headers:
X-Request-Id:
description: Correlation id for this request — echoed from X-Request-Id when you sent one, otherwise generated.
schema:
type: string
'403':
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
description: The key is valid but not authorized for this provider or this mode. A key carries exactly one provider; snapshot writes additionally require a `provider.ingest` key.
headers:
X-Request-Id:
description: Correlation id for this request — echoed from X-Request-Id when you sent one, otherwise generated.
schema:
type: string
'404':
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
description: No such event for your provider.
headers:
X-Request-Id:
description: Correlation id for this request — echoed from X-Request-Id when you sent one, otherwise generated.
schema:
type: string
'429':
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
description: 'Throttled. The per-key request-rate limit is enforced at the edge and answers first: exceeding it returns a `429` from the edge as `application/problem+json` of `type` `rate-limited` (no `Jenni-Version`) with the headers `x-ratelimit-limit`, `x-ratelimit-remaining` and `x-ratelimit-reset` (seconds until the window resets) — no `Retry-After`. A `429` carrying `Jenni-Version` and `Retry-After` was issued by provider-bff''s in-pod fallback limiter behind the edge; its `type` is the same `rate-limited`.'
headers:
Retry-After:
description: 'Seconds to wait before retrying. Present on every `429` provider-bff issues itself (problem+json body: `daily-budget-exhausted`, `sandbox-quota-exceeded`, `rate-limited`); ABSENT on the edge''s per-key rate-limit `429` (problem+json `rate-limited` without `Jenni-Version`) — read `x-ratelimit-reset` there instead. On `409` it is present only for `idempotency-in-progress` (an identical request still in flight) — the other 409s are permanent and carry no Retry-After.'
schema:
type: string
X-Request-Id:
description: 'Correlation id for this request — same value as `x-jenni-request-id` and the body `request_id`, on every `429`: provider-bff echoes it on the ones it issues and the edge stamps it on its own per-key rate-limit `429`. Quote it when you contact support.'
schema:
type: string
x-ratelimit-limit:
description: 'Edge per-key rate limit, IETF draft-03 `RateLimit` compound form: the applied limit, then each configured policy as `<n>;w=<window-seconds>` — e.g. `100, 100;w=60, 1000;w=60`. A STRING, not an integer; parse the first number for the limit. Present only on the edge''s `429` (problem+json `rate-limited`, no `Jenni-Version`).'
schema:
example: 100, 100;w=60, 1000;w=60
type: string
x-ratelimit-remaining:
description: Requests left in the current window for your key (`0` on the edge `429`). Present only on the edge's `429` (problem+json `rate-limited`, no `Jenni-Version`).
schema:
example: '0'
type: string
x-ratelimit-reset:
description: Seconds until the per-key window resets — the value to wait on, since the edge `429` carries no `Retry-After`. Present only on the edge's `429` (problem+json `rate-limited`, no `Jenni-Version`).
schema:
example: '4'
type: string
'500':
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
description: Unexpected internal error. Safe to retry; quote request_id if it persists.
headers:
X-Request-Id:
description: Correlation id for this request — echoed from X-Request-Id when you sent one, otherwise generated.
schema:
type: string
'503':
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
description: A dependency JENNi needs is unavailable (identity-service, or the catalog write layer). Fail-closed and retryable — retry with backoff.
headers:
X-Request-Id:
description: Correlation id for this request — echoed from X-Request-Id when you sent one, otherwise generated.
schema:
type: string
summary: Get the status of one submitted item
tags:
- Events
x-jenni-modes:
- write
- ingest
- read
x-required-scope: provider.write:{provider_id}, provider.ingest:{provider_id} or provider.read:{provider_id}
components:
schemas:
TrimmedProblem:
description: The trimmed {type, detail} problem carried on a per-item rejection. Same `type` vocabulary as the full Problem.
properties:
detail:
example: one of upc/gtin is required
type: string
type:
example: https://jenni.com/problem/validation-failed
format: uri
type: string
required:
- type
- detail
title: TrimmedProblem
type: object
ProblemError:
description: One field-level validation failure.
properties:
detail:
example: must be >= 0
type: string
field:
description: Dotted path to the offending field, or '(root)'.
example: items.0.quantity
type: string
required:
- field
- detail
title: ProblemError
type: object
Problem:
description: RFC-7807 problem details. EVERY 4xx and 5xx on this API returns this shape as `application/problem+json`. Branch on the stable `type` URI, never on `title` or `detail` (both are human-facing and may change).
properties:
active_key_ids:
description: 'Extension member on `active-key-cap`: the key ids already active for the tenant, so a client can revoke one without re-listing.'
example:
- 2f1c...
- 9a44...
items:
type: string
type: array
detail:
description: What went wrong for THIS request.
type: string
errors:
description: Field-level detail; present on validation failures.
items:
$ref: '#/components/schemas/ProblemError'
type: array
instance:
description: The request path that produced the problem.
type: string
limit:
description: 'Extension member on `sandbox-quota-exceeded`: the sandbox tenant''s daily item quota.'
example: 1000
type: integer
request_id:
description: Correlation id, also echoed in the X-Request-Id response header. Quote it in support requests.
type: string
resets_at:
description: 'Extension member on `sandbox-quota-exceeded`: when `used` resets — the next UTC midnight, RFC 3339.'
example: '2026-09-17T00:00:00.000Z'
format: date-time
type: string
status:
description: Mirrors the HTTP status.
type: integer
title:
description: Short human summary. Do not branch on it.
type: string
type:
description: Stable problem-type URI — the machine-readable error identity. See the error catalogue in the API reference.
example: https://jenni.com/problem/run-not-open
format: uri
type: string
used:
description: 'Extension member on `sandbox-quota-exceeded`: items already accepted for this sandbox tenant today (UTC). The refused batch is NOT included — it was not applied.'
example: 800
type: integer
required:
- type
- title
- status
- detail
- instance
- request_id
title: Problem
type: object
EventStatusView:
description: Delivery status for one previously submitted item, looked up by its event_id.
properties:
accepted_offset:
description: Decimal string; see ItemResult.accepted_offset.
type:
- 'null'
- string
event_id:
type: string
failure:
anyOf:
- $ref: '#/components/schemas/TrimmedProblem'
- type: 'null'
description: Populated only when status=failed.
last_transition_at:
format: date-time
type: string
status:
description: 'IN PRACTICE ONLY `pending` AND `failed` ARE RETURNED TODAY. An accepted item reports `pending` indefinitely: no downstream consumer transitions it, so `published`/`consumed` are reserved for a future release and are never emitted. `pending` therefore means ''accepted by JENNi'', NOT ''still in flight''.'
enum:
- pending
- published
- consumed
- failed
type: string
required:
- event_id
- status
- last_transition_at
- accepted_offset
- failure
title: EventStatusView
type: object
securitySchemes:
PartnerApiKey:
description: 'Your partner API key, sent as `{keyId}.{secret}` — the key id, one dot, then the secret. The value is split on the FIRST dot only, so a secret that itself contains dots is handled correctly; do not trim or re-encode it. The key determines your tenant and your provider: there is no tenant parameter anywhere in this API. Keys are revoked immediately when rotated, so treat a 401 as final rather than retrying it.'
in: header
name: X-Jenni-Api-Key
type: apiKey
PartnerBearer:
bearerFormat: JWT
description: 'The partner portal session token, sent as `Authorization: Bearer <token>`. It is issued by the portal login (identity-service `/v1/partner/auth/login`) and expires after an hour; refresh it with the refresh token rather than logging in again. It authenticates the `/provider/v1/portal/*` routes ONLY — an API key is refused there with `403 missing-scope`, and this token is refused on every other route with `401 invalid-auth`. The two credentials never substitute for each other.'
scheme: bearer
type: http
x-jenni-field-groups:
core:
cost: 2
fields:
- jenni_product_id
- jenni_product_store_id
- jenni_store_id
- store_id
- in_stock
- modified_at
groups:
- cost: 1
fields:
- gtin
- upc
name: identifiers
- cost: 3
fields:
- title
- brand
- color
- size
- product_link
name: content
- cost: 2
fields:
- price
- sale_price
name: pricing
- cost: 1
fields:
- inventory
name: availability
- cost: 1
fields:
- sku
- source_run_id
name: attribution
x-tagGroups:
- name: Get started
tags:
- Quickstart
- Authentication
- Scopes
- Platform basics
- Sandbox
- name: POS systems
tags:
- POS integration brief
- 'POS: catalog & inventory'
- Retailers & claim links
- Retailers
- Stores
- Runs
- Catalog
- Inventory
- name: Brands
tags:
- Brands
- Product readiness
- name: Marketplaces & channels
tags:
- Marketplace & channel
- Channel
- Events
- Webhooks
- Webhooks guide
- name: Fulfillment & delivery
tags:
- Fulfillment & delivery
- name: Reference
tags:
- Errors
- Idempotency
- Pagination
- Rate limits
- Versioning
- Support
- Changelog
- Account
- Portal
- Brand portal
x-jenni-portal:
injected_by: jenni-developer-portal/inject_guides.py
sources:
- pos-integration-brief.md
- pos-catalog-inventory.md
- brands.md
- product-readiness.md
- marketplace-channel.md
- fulfillment-delivery.md
- platform-basics.md
- sandbox.md
- retailers-and-claims.md
- quickstart.md
- authentication.md
- scopes.md
- errors.md
- idempotency.md
- pagination.md
- rate-limits.md
- versioning.md
- webhooks.md
- support.md
- CHANGELOG.md
- landing.md
source_headings:
- POS integration brief
- 'Guide A — POS systems: catalog and inventory ingestion'
- 'Guide D — Brands: product content through the ingestion lane'
- Product readiness — what makes a product sellable
- Marketplace and channel reads
- Guide C — Fulfillment and delivery providers
- Platform basics
- Sandbox — what a self-serve key can do, what it cannot, and how to leave it
- 'Guide E — Retailers: register a merchant and hand them a claim link'
- Quickstart
- Authentication
- Scopes
- Errors
- Idempotency
- Pagination
- Rate limits
- Versioning
- Webhooks guide
- Support
- Changelog
- JENNi Partner API
landing: landing.md