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.

GitHub OpenAPI

Operations 1

GET /provider/v1/me Key: what this key can do #

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/jennipro-com:jennipro-com-account-api"
All apis
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.

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

jennipro-com-account-api-openapi.yml Raw ↑
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