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/medianfi-com:medianfi-com-checkout-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: 3.2.0
info:
title: Median Checkout API
version: 0.1.0
description: Production-grade Node.js + Express API starter template
license:
name: Proprietary
servers:
- url: /api/v1
security: []
tags:
- name: Checkout
paths:
/companies/{companyId}/checkout/subscription:
parameters:
- $ref: '#/components/parameters/companyId'
get:
operationId: getCheckoutSubscription
summary: Get the company's active Median subscription, with plan + payment method
description: 'Returns the joined view of a company''s Median (SaaS) subscription,
shaped for the Billing & Plan card in the customer app. When the
local median_subscriptions row is missing but the company already
has a Stripe customer, the service resyncs from Stripe before
returning (self-heals missed webhook deliveries).'
tags:
- Checkout
security:
- bearerAuth: []
responses:
'200':
description: Subscription (null when the company has none)
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
enum:
- true
data:
oneOf:
- $ref: '#/components/schemas/CheckoutSubscriptionResponse'
- type: 'null'
required:
- success
- data
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/companies/{companyId}/checkout/custom-offer:
parameters:
- $ref: '#/components/parameters/companyId'
get:
operationId: getCustomPricingOffer
summary: Get the sales-led custom pricing offer for the authenticated user
description: 'Returns the active sales-led custom pricing offer matched by the
authenticated user''s email, or null. When present, the onboarding price
screen shows a single fixed-price card instead of the tier list.'
tags:
- Checkout
security:
- bearerAuth: []
responses:
'200':
description: Custom offer (null when none is active for this email)
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
enum:
- true
data:
oneOf:
- type: object
additionalProperties: false
properties:
monthlyPriceCents:
type: integer
minimum: 1
maximum: 100000000
trialDays:
type: integer
minimum: 0
maximum: 365
required:
- monthlyPriceCents
- trialDays
- type: 'null'
required:
- success
- data
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/companies/{companyId}/checkout/custom-offer-session:
parameters:
- $ref: '#/components/parameters/companyId'
post:
operationId: createCustomOfferCheckoutSession
summary: Start Stripe Checkout at the sales-led custom flat monthly price
description: 'Creates a Stripe Checkout session billing the customer''s agreed flat
monthly price (locked indefinitely), looked up by the authenticated
email and never trusted from the client. Adds a 30-day trial when the
offer grants the first month free. The price attaches to the shared
Median catalog product, like the usage-based subscription.'
tags:
- Checkout
security:
- bearerAuth: []
responses:
'200':
description: Stripe Checkout URL
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
enum:
- true
data:
type: object
properties:
url:
type:
- string
- 'null'
required:
- url
required:
- success
- data
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/companies/{companyId}/checkout/quote:
parameters:
- $ref: '#/components/parameters/companyId'
get:
operationId: getPricingQuote
deprecated: true
summary: Retired usage-derived price quote (always 410)
description: '#3647: the $55/account + $0.60/line meter was retired on 2026-10-21.
New signups are priced by revenue tier, so this quote no longer exists.
It always answers 410 `METERED_PRICING_RETIRED`, as does
`POST /checkout/custom-session`, the retired checkout for these quotes.'
tags:
- Checkout
security:
- bearerAuth: []
responses:
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'410':
description: Per-account pricing is retired.
content:
application/json:
schema:
$ref: '#/components/schemas/ApiErrorResponse'
/companies/{companyId}/pricing/statement-estimate:
parameters:
- $ref: '#/components/parameters/companyId'
post:
operationId: estimateStatement
summary: Estimate monthly transactions from an uploaded statement
description: 'Line-counts a recent statement for one unconnected account into a monthly
transaction estimate, so a statement account still contributes to the
onboarding quote. With a documentId the server reads the uploaded
statement itself: text-layer PDFs and text/CSV exports are counted
directly; scanned PDFs/images go through a best-effort LLM count. Falls
back to a conservative per-account default when the statement cannot be
counted.'
tags:
- Checkout
security:
- bearerAuth: []
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
documentId:
type: string
format: uuid
description: An uploaded statement document to count server-side (takes precedence over rawText/lineCount).
rawText:
type: string
description: Raw statement text to line-count.
lineCount:
type: integer
minimum: 0
description: A pre-counted line total (when counted client-side).
monthsCovered:
type: integer
minimum: 1
description: Months the statement covers; the line count is divided by this.
responses:
'200':
description: The per-account monthly transaction estimate
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
enum:
- true
data:
type: object
properties:
monthlyTransactions:
type: integer
minimum: 0
source:
type: string
enum:
- parsed
- fallback
required:
- monthlyTransactions
- source
required:
- success
- data
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'422':
$ref: '#/components/responses/ValidationError'
/companies/{companyId}/pricing/usage-vs-quote:
parameters:
- $ref: '#/components/parameters/companyId'
get:
operationId: getUsageVsQuote
summary: Compare actual posted entry volume against the quoted volume
description: 'Returns the quoted monthly entry volume vs actual posted ledger lines over
the trailing complete months, with the re-quote threshold (150% monthly,
200% annual) and an advisory breach flag set when every trailing month
(>= 2) exceeds the threshold. V1 surfaces this for display only and never
auto-rebills.'
tags:
- Checkout
security:
- bearerAuth: []
responses:
'200':
description: The usage-vs-quote comparison
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
enum:
- true
data:
type: object
properties:
hasQuotedVolume:
type: boolean
billingInterval:
type:
- string
- 'null'
quotedMonthlyEntries:
type:
- integer
- 'null'
minimum: 0
thresholdPct:
type:
- integer
- 'null'
thresholdEntries:
type:
- integer
- 'null'
minimum: 0
months:
type: array
items:
type: object
properties:
month:
type: string
actualEntries:
type: integer
minimum: 0
ratioPct:
type:
- integer
- 'null'
required:
- month
- actualEntries
- ratioPct
breached:
type: boolean
required:
- hasQuotedVolume
- billingInterval
- quotedMonthlyEntries
- thresholdPct
- thresholdEntries
- months
- breached
required:
- success
- data
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
components:
schemas:
ApiErrorResponse:
type: object
required:
- success
- error
properties:
success:
type: boolean
enum:
- false
error:
$ref: '#/components/schemas/ProblemDetail'
CheckoutSubscriptionResponse:
type: object
required:
- id
- companyId
- status
- stripeSubscriptionStatus
- platformItemState
- platformItemId
- providerObservedAt
- platformTermsVerifiedAt
- stripeSubscriptionId
- stripePriceId
- currentPeriodStart
- currentPeriodEnd
- cancelledAt
- planTier
- plan
- billing
- paymentMethod
- paymentMethodStatus
- paymentMethodSummary
- source
properties:
id:
type:
- string
- 'null'
format: uuid
companyId:
type: string
format: uuid
status:
type: string
enum:
- active
- past_due
- cancelled
- trialing
- incomplete
- needs_attention
description: Missing or ambiguous platform items project needs_attention; cancellation takes precedence.
stripeSubscriptionStatus:
type:
- string
- 'null'
description: Exact provider status from the last observation.
platformItemState:
type: string
enum:
- unverified
- present
- missing
- ambiguous
platformItemId:
type:
- string
- 'null'
providerObservedAt:
type:
- string
- 'null'
format: date-time
platformTermsVerifiedAt:
type:
- string
- 'null'
format: date-time
stripeSubscriptionId:
type:
- string
- 'null'
stripePriceId:
type:
- string
- 'null'
currentPeriodStart:
type:
- string
- 'null'
format: date-time
currentPeriodEnd:
type:
- string
- 'null'
format: date-time
cancelledAt:
type:
- string
- 'null'
format: date-time
planTier:
type:
- string
- 'null'
plan:
oneOf:
- $ref: '#/components/schemas/CheckoutSubscriptionPlan'
- type: 'null'
billing:
description: 'Null when the billed amount cannot be read from Stripe (no
subscription, or Stripe unavailable). Clients must not present the
plan''s list price as the billed amount in that case.
'
oneOf:
- $ref: '#/components/schemas/CheckoutSubscriptionBilling'
- type: 'null'
paymentMethod:
oneOf:
- $ref: '#/components/schemas/CheckoutSubscriptionPaymentMethod'
- type: 'null'
paymentMethodStatus:
type: string
enum:
- available
- none
- unavailable
- not_configured
description: 'Whether Stripe returned a payment method, confirmed that none is
configured, or could not establish the answer. The not_configured
state means the company has no Stripe customer id; show "No Stripe
billing on file" without a Stripe portal action. An unavailable
result must not be presented as "No payment method". Neither state
permits displaying the stored payment-method summary as current.
'
paymentMethodSummary:
type:
- string
- 'null'
source:
type: string
enum:
- median_subscription
- stripe_resync
- billing_plan_fallback
ProblemDetail:
type: object
properties:
type:
type: string
title:
type: string
description: 'For application errors, a stable UPPER_SNAKE machine code (for example `NOT_FOUND`, `FORECAST_NOT_FOUND`, `VALIDATION_ERROR`). Clients branch on this, never on `detail` wording. A few transport level responses use a short human title instead.
'
status:
type: integer
detail:
type: string
description: 'Human-readable explanation; correlate a failure with `traceId`. A NOT_FOUND-family 404 names the resource type (for example "Company not found"), not its id. Some legacy messages still include an id (tracked in #2666).
'
instance:
type: string
traceId:
type: string
retryAfter:
type: integer
minimum: 0
description: Whole seconds until retry is allowed for a 429 response
errors:
type: array
items:
type: object
properties:
field:
type: string
message:
type: string
extensions:
type: object
additionalProperties: true
CheckoutSubscriptionPaymentMethod:
type: object
required:
- brand
- last4
- expMonth
- expYear
properties:
brand:
type: string
last4:
type: string
expMonth:
type:
- integer
- 'null'
minimum: 1
maximum: 12
expYear:
type:
- integer
- 'null'
CheckoutSubscriptionBilling:
type: object
description: 'What Stripe actually charges the subscription each period. Differs from
the plan''s list price whenever a coupon applies — a comped company has
an effectiveAmountCents of 0 against a non-zero listAmountCents.
'
required:
- listAmountCents
- effectiveAmountCents
- interval
- discountLabel
- trialEndsAt
- discountEndsAt
properties:
listAmountCents:
type: integer
minimum: 0
effectiveAmountCents:
description: 'The recurring charge each period once billing starts. While
trialEndsAt is set this amount is not yet being charged.
'
type: integer
minimum: 0
interval:
type: string
enum:
- month
- year
discountLabel:
type:
- string
- 'null'
trialEndsAt:
description: 'Date the trial ends and effectiveAmountCents starts being charged.
Non-null means nothing is due until then, and clients must show both
the zero due now and the recurring amount with this date.
'
type:
- string
- 'null'
format: date-time
discountEndsAt:
description: 'Date the coupon expires and listAmountCents resumes. Null when the
coupon runs for the life of the subscription.
'
type:
- string
- 'null'
format: date-time
CheckoutSubscriptionPlan:
type: object
required:
- id
- name
- priceCents
- interval
- features
properties:
id:
type: string
format: uuid
name:
type: string
priceCents:
type: integer
minimum: 0
interval:
type: string
enum:
- month
- year
features:
type: array
items:
type: string
responses:
BadRequest:
description: Request failed validation
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ProblemDetail'
Forbidden:
description: Insufficient permissions or not a company member
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ProblemDetail'
Unauthorized:
description: Authentication required
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ProblemDetail'
ValidationError:
description: Request failed validation
content:
application/problem+json:
schema:
$ref: '#/components/schemas/ProblemDetail'
parameters:
companyId:
name: companyId
in: path
required: true
schema:
type: string
format: uuid
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
serviceApiKey:
type: apiKey
in: header
name: X-API-Key
description: 'Persistent credential for the configured cloud-bookkeeping service principal. The key must carry accountant:* and is accepted only on accountant operations that explicitly list this scheme. Ordinary API keys, and service keys presented to bearer-only operations, are denied.
'
refreshSessionCookie:
type: apiKey
in: cookie
name: __Secure-median_refresh
description: 'Exact production HttpOnly refresh-session cookie. Local non-secure development uses median_refresh.
'
passkeyEnrollmentGrantCookie:
type: apiKey
in: cookie
name: __Secure-median_passkey_enrollment
description: 'Exact production HttpOnly passkey-enrollment grant cookie. Local non-secure development uses median_passkey_enrollment.
'
webAuthnCeremonyCookie:
type: apiKey
in: cookie
name: __Secure-median_webauthn
description: 'Exact production HttpOnly WebAuthn ceremony cookie. Local non-secure development uses median_webauthn.
'
x-median-quarantined-authorities:
decision: ADR-0001-model-a
runtimeStatus: unmounted
description: 'Migrations 267-272 modeled legal entities as separate company tenants. These paths are not runtime API authority while their preserved accounting math is re-keyed to legal entities inside one company workspace.
'
paths:
- /companies/{companyId}/consolidation-groups
- /companies/{companyId}/consolidation-groups/{groupId}/versions/{groupVersionId}/report-rollups
- /companies/{companyId}/consolidation-groups/{groupId}/versions/{groupVersionId}/report-rollups/{rollupId}
- /companies/{companyId}/consolidation-groups/{groupId}/versions/{groupVersionId}/translation-adjustments
- /companies/{companyId}/consolidation-groups/{groupId}/versions/{groupVersionId}/translation-adjustments/{adjustmentId}
x-median-demo-parity-customer-surface:
runtimeStatus: mounted-lifecycle-only
description: 'v2 customer-app capabilities available to JWT-authenticated members of any active or test company — the v2 app is the product, not a rollout cohort, so no allowlist applies to the markerless path. Production customer calls omit X-Median-App-Surface. Test-company calls may carry X-Median-App-Surface: qa to select the QA bundle; that marker branch alone still requires membership in CUSTOMER_APP_DEMO_PARITY_COMPANY_IDS. An API key, unknown marker, malformed company id, inactive company, missing gate dependency, or failed company lookup receives the standard hidden-resource 404 contract. The held surfaces riding these prefixes (AR/AP, Forecast, Insights) carry their own additional allowlist gate — see x-median-forecast-hold and x-median-ar-ap-hold.
'
headerContract: '#/components/parameters/demoParityCustomerAppSurface'
routePrefixes:
- /companies/{companyId}/ar
- /companies/{companyId}/ap
- /companies/{companyId}/customers
- /companies/{companyId}/vendors
- /companies/{companyId}/contracts
- /companies/{companyId}/receivables
- /companies/{companyId}/entities
- /companies/{companyId}/currencies
- /companies/{companyId}/reporting-scopes
- /companies/{companyId}/reporting-scopes/{reportingScopeId}/versions/{versionId}/rollups
- /companies/{companyId}/reporting-scopes/{reportingScopeId}/versions/{versionId}/translation-adjustments
- /companies/{companyId}/presentation-report-snapshots
- /companies/{companyId}/reporting-trial-balances
- /companies/{companyId}/reporting-scopes/{reportingScopeId}/versions/{versionId}/trial-balances
- /companies/{companyId}/reporting-scopes/{reportingScopeId}/versions/{versionId}/intercompany
- /companies/{companyId}/weekly-email
- /companies/{companyId}/forecast
- /companies/{companyId}/books
- /companies/{companyId}/sub-ledgers
x-median-forecast-hold:
runtimeStatus: mounted-only-with-server-gate
description: 'Forecast is held. Beyond the lifecycle-only customer surface above, every operation under the prefix below also requires the company to appear in the held-surface allowlist CUSTOMER_APP_DEMO_PARITY_COMPANY_IDS — the one operator-set list that opens AR/AP, Forecast and Insights (the legacy CUSTOMER_APP_FORECAST_COMPANY_IDS variable is no longer consulted). It is empty by default, so the v2 rollout alone never exposes Forecast. A company off the list receives the same hidden-resource 404 contract as one for which the surface does not exist. The auth-context featureFlags key `forecast` mirrors this verdict for the customer app shell.
'
featureFlag: forecast
routePrefixes:
- /companies/{companyId}/forecast
x-median-ar-ap-hold:
runtimeStatus: mounted-only-with-server-gate
description: 'Receivables/Payables are held behind TWO required authorities. Beyond the lifecycle-only customer surface above, every operation under the prefixes below requires the company to appear in the held-surface allowlist CUSTOMER_APP_DEMO_PARITY_COMPANY_IDS (requireDemoParityAllowlistedCompany) AND to satisfy the company_subledger_flags conversion chain (requireArApItemsEnabled — the same authority the public /pay page, aging and the accountant portal consult); failing either answers the opaque 404. The auth-context featureFlags key `arApItems` mirrors exactly that conjunction, resolved server-side, so the customer shell hides the Receivables/Payables nav and pages instead of offering menu entries these routes 404. Default is FALSE for every company while the list is empty or the conversion has not run.
'
featureFlag: arApItems
routePrefixes:
- /companies/{companyId}/ar
- /companies/{companyId}/ap
- /companies/{companyId}/customers
- /companies/{companyId}/vendors
- /companies/{companyId}/contracts
- /companies/{companyId}/receivables
x-median-demo-parity-qa-only:
runtimeStatus: mounted-only-with-server-gate
description: 'Strict test-company-only routes. Every interactive call requires X-Median-App-Surface: qa in addition to JWT authentication, the server allowlist, and persisted client_status=test. A copied header, API key, active company, missing gate dependency, or failed company lookup receives the standard hidden-resource 404 contract.
'
headerContract: '#/components/parameters/qaAppSurface'
routePrefixes:
- /companies/{companyId}/gl-accounts/{id}/monetary-classification
- /companies/{companyId}/fx-account-roles
- /companies/{companyId}/gl-periods/{periodId}/fx-revaluation
- /accountant/companies/{customerId}/gl-accounts/{id}/monetary-classification
- /accountant/companies/{customerId}/fx-account-roles
- /accountant/companies/{customerId}/gl-periods/{periodId}/fx-revaluation
- /accountant/companies/{customerId}/ar/{id}
- /accountant/companies/{customerId}/ap/{id}
x-median-onboarding-v3-surface:
runtimeStatus: promoted
promotionFlag: ONBOARDING_V3_QA_ONLY=false
description: 'Promoted. The deployment this contract describes runs ONBOARDING_V3_QA_ONLY=false, so none of the onboarding-v3 auth, pricing, or setup paths below requires X-Median-App-Surface: qa. An unmarked request is served and reaches ordinary authentication, so an unauthenticated call now answers 401 rather than the hidden-resource 404. Mounting is still conditional: with ONBOARDING_V3_ENABLED false every path below returns the hidden-resource 404 before body parsing or authentication. The header stays defined and is still honoured, because a deployment left at ONBOARDING_V3_QA_ONLY=true keeps requiring it on every path below and 404s unmarked traffic; setting that flag back to true is the rollback. On either setting the header is a pre-company bundle marker, never tenant authorization.
'
headerContract: '#/components/parameters/onboardingV3QaSurface'
routePaths:
- /auth/otp/start
- /auth/otp/verify
- /auth/passkeys/register/options
- /auth/passkeys/register/verify
- /auth/passkeys/assert/options
- /auth/passkeys/assert/verify
- /onboarding/v3/quote
- /onboarding/v3/offer/resolve
- /onboarding/v3/flow
- /onboarding/v3/setup-intent
- /onboarding/v3/subscribe
- /onboarding/v3/test-account-activation
- /onboarding/v3/checkout-session
- /onboarding/v3/checkout-session/complete
- /onboarding/v3/setup-state
- /onboarding/v3/delegate-invites
- /onboarding/v3/delegate-invites/{inviteId}
- /onboarding/v3/delegate/resolve
- /onboarding/v3/delegate
- /onboarding/v3/task-context
- /onboarding/v3/delegate/task-context
- /onboarding/v3/banks/plaid/link-token
- /onboarding/v3/banks/plaid/exchange
- /onboarding/v3/banks/commit
- /onboarding/v3/documents/google-workspace/auth-url
- /onboarding/v3/documents/google-workspace/callback
- /onboarding/v3/books/quickbooks/auth-url
- /onboarding/v3/books/quickbooks/callback
- /onboarding/v3/books/xero/auth-url
- /onboarding/v3/books/xero/callback
- /onboarding/v3/welcome-call
- /onboarding/v3/welcome-call/embed
- /onboarding/v3/books-progress
- /onboarding/v3/books/open
- /companies/{companyId}/onboarding/v3/questions
- /companies/{companyId}/onboarding/v3/questions/{questionId}/answer
- /companies/{companyId}/onboarding/v3/review-items