JENNi Account API
What this key is. Authentication-only: a key with no provider scope still gets an answer here, so the portal and the partner can see why everything else 403s.
What this key is. Authentication-only: a key with no provider scope still gets an answer here, so the portal and the partner can see why everything else 403s.
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-account-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 Account 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: 'What this key is. Authentication-only: a key with no provider scope still gets an answer here, so the portal and the partner can see why everything else 403s.'
name: Account
paths:
/provider/v1/me:
get:
description: 'Returns the capabilities of the key on this request: which provider it acts for, which credential mode it holds, its scopes verbatim, and which field groups it may receive.
**This is the endpoint the partner portal renders from.** Each operation in this document declares `x-jenni-modes`; the portal shows you an operation when one of your `modes` appears in it, so the documentation you read is the documentation for your key. Field groups you are not entitled to are shown greyed out rather than hidden, so you can see what exists and ask for it.
`provider_id: null` with an empty `modes` is the diagnosis for a key that authenticates but 403s everywhere: it was issued without a `provider.*` scope. Send that response to support and it is a one-line fix at our end.
`limits` reports the two throttles on your key: `rate` is the request-rate limit the edge applies to your key''s `daily_budget.tier` (declared from the gateway policy, not measured per call) — a `sandbox` key gets a smaller allowance counted across your whole tenant (`per: "tenant"`), every other tier the per-key rule — and `daily_budget` is the per-key daily row budget on the paged reads — `used` is live when the budget is enabled and `null` otherwise. Plan a sync against `daily_budget.rows`: it is the threshold for your key''s `tier` (`sandbox` keys have their own, smaller allowance; `production` and `none` are budgeted at the live threshold), and `exempt: true` means this key is never counted or refused. A `429` of `type` `rate-limited` without `Jenni-Version`, carrying `x-ratelimit-*` headers, is the edge rate limit (wait `x-ratelimit-reset` seconds); a `429` carrying `Jenni-Version` came from provider-bff and its `type` says which: `daily-budget-exhausted` or the in-pod fallback `rate-limited`, both with `Retry-After`.
It costs nothing against your own limits and does not count toward the daily budget — safe to call on every page load. (When the daily budget is enabled it performs one read of the budget ledger to fill `used`; if that read fails, `used` is `null` and the rest of the response is unaffected.)'
operationId: getMe
parameters:
- 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/KeyCapabilityView'
description: Your key's capabilities.
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
'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: 'Key: what this key can do'
tags:
- Account
x-jenni-modes:
- write
- ingest
- read
x-required-scope: any valid partner key (no scope required)
components:
schemas:
KeyRateLimitView:
description: 'The request-rate limit applied to your key at the edge, as declared in the gateway''s rate-limit policy — a declaration, not a measurement: provider-bff does not read the live counter. Which rule you get follows `limits.daily_budget.tier`: a `sandbox` key is limited to the smaller allowance counted across your whole account (`per: "tenant"`), every other tier to the per-key rule (`per: "key"`). Several edge rules can apply at once; this is the one that binds first for your key. 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 itself and its `type` says which limit: `daily-budget-exhausted` (the per-key daily row budget) or `rate-limited` (the in-pod fallback limiter behind the edge).'
properties:
per:
description: 'What the limit is counted against. `key` — each API key has its own allowance (the rule for every tier but `sandbox`). `tenant` — the allowance is shared across every key on your account, which is how `sandbox` keys are limited: one developer account cannot buy more throughput by minting more sandbox keys.'
enum:
- key
- tenant
type: string
requests:
description: Requests allowed per `window`.
type: integer
window:
description: The window the count is taken over, e.g. `1m`.
type: string
required:
- requests
- window
- per
title: KeyRateLimitView
type: object
KeyLimitsView:
description: 'The two throttles that apply to your key. Both surface as a problem+json `429`; tell them apart by `Jenni-Version`: the edge rate limit is a `429` of `type` `rate-limited` without `Jenni-Version`, with `x-ratelimit-*` headers (read `x-ratelimit-reset`); the daily budget is a `429` of `type` `daily-budget-exhausted` carrying `Jenni-Version` and `Retry-After`.'
properties:
daily_budget:
$ref: '#/components/schemas/KeyDailyBudgetView'
rate:
$ref: '#/components/schemas/KeyRateLimitView'
required:
- rate
- daily_budget
title: KeyLimitsView
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
KeyCapabilityView:
description: 'What the key on THIS request can do. The partner portal calls this to render documentation for one partner: it hides operations whose `x-jenni-modes` does not include one of your `modes`, and marks field groups you are not entitled to.'
properties:
core_cost:
description: What a read call costs before any group is added.
type: integer
core_fields:
description: Present on every read response; no `include` value can remove them.
items:
type: string
type: array
field_groups:
items:
$ref: '#/components/schemas/FieldGroupView'
type: array
key_id:
description: The UUID half of your X-Jenni-Api-Key, before the dot.
type: string
limits:
$ref: '#/components/schemas/KeyLimitsView'
modes:
description: write = full catalog writes; ingest = run-scoped submission; read = reconciliation reads only.
items:
enum:
- write
- ingest
- read
type: string
type: array
provider_id:
description: The provider slug this key acts for. NULL means the key carries no usable provider scope (or, wrongly, more than one) — every provider operation will 403 until an operator re-issues it.
type:
- 'null'
- string
scopes:
description: Your scopes verbatim.
items:
type: string
type: array
tenant_id:
description: The identity tenant UUID bound to this authenticated key.
format: uuid
type: string
required:
- key_id
- tenant_id
- provider_id
- modes
- scopes
- field_groups
- core_fields
- core_cost
- limits
title: KeyCapabilityView
type: object
KeyDailyBudgetView:
description: 'The per-key daily ROW budget on the paged reads (`GET /provider/v1/products`, `GET /provider/v1/inventory/levels`, the `/channel/v1` reads). Every `items[]` row served counts; once `used` reaches `rows` the next read is a `429` with `type` `daily-budget-exhausted` and a `Retry-After` of the seconds until `resets_at`. `rows` is the threshold for your key''s `tier`: a `sandbox` key has its own, smaller allowance; every other tier is budgeted at the live threshold. An `exempt` key is never counted or refused.'
properties:
enabled:
description: Whether the budget is enforced right now. When false no read is ever refused for it and `used` is `null`.
type: boolean
exempt:
description: 'true when this key is on the operator''s exemption list: it is never counted against the budget and never refused for it, and `used` is `null`.'
type: boolean
resets_at:
description: When `used` resets — the next UTC midnight, RFC 3339.
format: date-time
type: string
rows:
description: Rows per UTC day this key may be served — the threshold for `tier`.
type: integer
tier:
description: 'The tier the edge stamped on this request''s key: `sandbox` (a self-registered developer account), `production` (a provisioned partner key), or `none` when no tier was stamped. Only `sandbox` selects the smaller allowance.'
enum:
- sandbox
- production
- none
type: string
used:
description: Rows served to this key so far today (UTC). `null` when the budget is not enforced for this key (`enabled` false, or `exempt` true), or when its ledger could not be read for this response.
type:
- 'null'
- integer
required:
- rows
- used
- resets_at
- enabled
- tier
- exempt
title: KeyDailyBudgetView
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
FieldGroupView:
description: One request-time field group, with whether YOUR key may receive it. Un-entitled groups are still listed so you can see what exists and ask for it.
properties:
cost:
description: What adding this group costs a read call, on top of core_cost.
type: integer
entitled:
description: true when your key holds the matching `catalog.<name>.read:` scope, or is a write/ingest key (which are entitled to every group).
type: boolean
fields:
description: The response fields this group carries.
items:
type: string
type: array
name:
description: Use this value in the `include` query parameter.
type: string
required:
- name
- fields
- cost
- entitled
title: FieldGroupView
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