OpenMercantil Billing API
Session-bound Stripe checkout, invoices and portal contracts. External actions are bounded, idempotent and never exposed through the public MCP.
Session-bound Stripe checkout, invoices and portal contracts. External actions are bounded, idempotent and never exposed through the public MCP.
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/openmercantil-billing-api"
curl "https://apis.io/api/v1/apis?limit=25"
Discovery needs no key. Ratings and market analysis are Pro.
openapi: 3.2.0
info:
title: Openmercantil Billing API
contact:
name: OpenMercantil
url: https://openmercantil.es/soporte
email: social@openmercantil.es
termsOfService: https://openmercantil.es/terminos-de-uso
x-refined-note:
- x-account-segment-contract differs across the merged source definitions and was not carried
- x-company-identity-contract differs across the merged source definitions and was not carried
- x-contract-status differs across the merged source definitions and was not carried
- x-corrections differs across the merged source definitions and was not carried
- x-dcat-catalog differs across the merged source definitions and was not carried
- x-language differs across the merged source definitions and was not carried
- x-methodology differs across the merged source definitions and was not carried
- x-publisher differs across the merged source definitions and was not carried
- x-rate-limit differs across the merged source definitions and was not carried
- x-sources differs across the merged source definitions and was not carried
- x-spatial differs across the merged source definitions and was not carried
- x-temporal differs across the merged source definitions and was not carried
version: '1.0'
description: 'Operations tagged Billing across 2 of this provider''s published API definitions: openmercantil-openapi-1.9.3.json, openmercantil-openapi.yml. Each path carries the servers of the definition it was published in.'
servers:
- url: https://openmercantil.es
description: Production
tags:
- name: Billing
description: Session-bound Stripe checkout, invoices and portal contracts. External actions are bounded, idempotent and never exposed through the public MCP.
paths:
/api/v1/donation:
post:
operationId: postDonationCheckout
tags:
- Billing
summary: Create a one-time donation Checkout session
description: Anonymous or authenticated. Body is capped at 16 KiB; Stripe work is fail-closed, budgeted by trusted client IP and idempotent within a 15-minute retry window. A supplied idempotency key is hashed and namespaced before Stripe.
x-plane: billing
security: []
parameters:
- name: Idempotency-Key
in: header
required: false
schema:
type: string
pattern: ^[A-Za-z0-9._:\-]{8,200}$
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
required:
- amount_cents
properties:
amount_cents:
type: integer
minimum: 100
maximum: 100000
message:
type: string
maxLength: 500
idempotency_key:
type: string
pattern: ^[A-Za-z0-9._:\-]{8,200}$
responses:
'200':
description: Checkout URL and Stripe session id
content:
application/json:
schema:
$ref: '#/components/schemas/StripeSessionResponse'
'400':
$ref: '#/components/responses/BadRequest'
'413':
description: Request body too large
'429':
$ref: '#/components/responses/TooManyRequests'
'503':
description: Action budget or Stripe unavailable
servers:
- url: https://openmercantil.es
description: Production
/api/v1/credits/checkout:
post:
operationId: postCreditsCheckout
tags:
- Billing
summary: Create an idempotent credit-pack Checkout session
description: Requires login and CSRF. The body is capped at 16 KiB and every external attempt consumes a strict per-user budget.
x-plane: billing
security:
- cookieAuth: []
parameters:
- $ref: '#/components/parameters/CsrfHeader'
- name: Idempotency-Key
in: header
required: false
schema:
type: string
pattern: ^[A-Za-z0-9._:\-]{8,200}$
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
required:
- pack
properties:
pack:
type: string
enum:
- pack_250
- pack_600
- pack_1500
- pack_4000
csrf:
type: string
idempotency_key:
type: string
pattern: ^[A-Za-z0-9._:\-]{8,200}$
responses:
'200':
description: Checkout URL and Stripe session id
content:
application/json:
schema:
$ref: '#/components/schemas/StripeSessionResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
description: Invalid CSRF token
'413':
description: Request body too large
'429':
$ref: '#/components/responses/TooManyRequests'
'503':
description: Action budget or Stripe unavailable
servers:
- url: https://openmercantil.es
description: Production
/api/v1/checkout:
post:
operationId: postSubscriptionCheckout
tags:
- Billing
summary: Create a subscription Checkout session
description: 'Requires a Secure, SameSite browser session and X-CSRF-Token. Trial policy is server-owned: clients cannot send trial_days or no_default_trial. Idempotency is scoped to user, plan and billing period before Stripe.'
x-plane: billing
security:
- cookieAuth: []
parameters:
- $ref: '#/components/parameters/CsrfHeader'
- name: Idempotency-Key
in: header
required: false
schema:
type: string
pattern: ^[A-Za-z0-9._:\-]{8,200}$
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
required:
- plan
properties:
plan:
type: string
enum:
- profesional
- max
- pro
- business
- agency
billing:
type: string
enum:
- monthly
- yearly
- anual
default: monthly
coupon:
type: string
maxLength: 80
csrf:
type: string
description: Body fallback accepted by the runtime; prefer X-CSRF-Token.
idempotency_key:
type: string
pattern: ^[A-Za-z0-9._:\-]{8,200}$
responses:
'200':
description: Checkout URL and Stripe session id
content:
application/json:
schema:
$ref: '#/components/schemas/StripeSessionResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'413':
description: Request body too large
'429':
$ref: '#/components/responses/TooManyRequests'
'503':
description: Action budget or Stripe unavailable
'403':
description: Invalid CSRF token
servers:
- url: https://openmercantil.es
description: Production
/api/v1/billing/invoices:
get:
operationId: getBillingInvoices
tags:
- Billing
summary: List the authenticated user's invoices and subscription
x-plane: billing
security:
- cookieAuth: []
responses:
'200':
description: Local billing projection
content:
application/json:
schema:
$ref: '#/components/schemas/BillingInvoicesResponse'
'401':
$ref: '#/components/responses/Unauthorized'
'429':
$ref: '#/components/responses/TooManyRequests'
servers:
- url: https://openmercantil.es
description: Production
/api/v1/billing/portal:
get:
operationId: getBillingPortal
tags:
- Billing
summary: Redirect to the authenticated user's Stripe portal
x-plane: billing
security:
- cookieAuth: []
responses:
'302':
description: Redirect to Stripe Customer Portal
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'429':
$ref: '#/components/responses/TooManyRequests'
'503':
description: Action budget or Stripe unavailable
post:
operationId: postBillingPortal
tags:
- Billing
summary: Create a Stripe portal session as JSON
x-plane: billing
security:
- cookieAuth: []
parameters:
- $ref: '#/components/parameters/CsrfHeader'
responses:
'200':
description: Stripe Customer Portal URL
content:
application/json:
schema:
$ref: '#/components/schemas/StripeSessionResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
description: Invalid CSRF token
'429':
$ref: '#/components/responses/TooManyRequests'
'503':
description: Action budget or Stripe unavailable
servers:
- url: https://openmercantil.es
description: Production
/api/v1/portal:
post:
operationId: postLegacyBillingPortal
tags:
- Billing
summary: Create a Stripe Customer Portal session through the legacy alias
description: Deprecated alias of `POST /api/v1/billing/portal`. Requires an authenticated browser session and a valid CSRF token supplied either in `X-CSRF-Token` or the form field `csrf`. New clients must use the canonical billing route.
deprecated: true
x-replaced-by: /api/v1/billing/portal
x-plane: billing
x-rate-limit: 'Per-user action budget: 5 attempts per 15 minutes and 30 per day'
x-mcp-excluded: true
security:
- cookieAuth: []
parameters:
- name: X-CSRF-Token
in: header
required: false
description: Required unless the same token is sent in the form field `csrf`.
schema:
type: string
minLength: 32
maxLength: 256
requestBody:
required: false
description: Legacy form fallback for CSRF. Prefer the header and an empty body.
content:
application/x-www-form-urlencoded:
schema:
type: object
additionalProperties: false
properties:
csrf:
type: string
minLength: 32
maxLength: 256
responses:
'200':
description: Stripe Customer Portal URL
content:
application/json:
schema:
$ref: '#/components/schemas/StripeSessionResponse'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
description: Invalid or missing CSRF token
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'429':
$ref: '#/components/responses/TooManyRequests'
'503':
description: Action-budget storage or Stripe Customer Portal is unavailable
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
servers:
- url: https://openmercantil.es
description: Production
/api/v1/stripe-webhook:
post:
operationId: receiveStripeWebhook
tags:
- Billing
summary: Receive a signed Stripe event
description: Provider callback, not an end-user API. The exact raw JSON body is capped at 512 KiB and verified against `Stripe-Signature` before durable inbox claim or side effects. Provider `event.id` supplies idempotency; terminal duplicates return 200, active leases return 503 for retry, and failures return 500 so Stripe retries. No webhook secret, signature, raw payload or internal exception is returned. This route is excluded from every public MCP surface.
x-plane: integration-provider-callback
x-provider: stripe
x-max-body-bytes: 524288
x-idempotency-key: Stripe event.id
x-log-policy: Redact Stripe-Signature and the raw request body
x-mcp-excluded: true
security: []
parameters:
- name: Stripe-Signature
in: header
required: true
description: Stripe-generated signature verified over the exact raw body. It is sensitive request metadata and must never be logged or echoed.
schema:
type: string
minLength: 1
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/StripeWebhookEventRequest'
responses:
'200':
description: Event accepted, deliberately ignored, or already terminal. All response bodies are non-secret acknowledgements.
content:
text/plain:
schema:
type: string
enum:
- ok
- duplicate (terminal idempotent skip)
- ignored (other source)
- credits ok
- donation ok
- subscription missing
- historical subscription ignored
- clawback rejected
- clawback
- ignored
- ignored invoice
- ignored payment failure
'400':
description: Signature missing/invalid, or the raw request stream cannot be read
content:
text/plain:
schema:
type: string
const: Bad signature
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
error: request_body_unreadable
'413':
description: Raw callback body exceeds 512 KiB
content:
application/json:
schema:
$ref: '#/components/schemas/RequestBodyTooLargeError'
example:
error: request_body_too_large
max_bytes: 524288
'500':
description: Inbox persistence or event processing failed; Stripe must retry
content:
text/plain:
schema:
type: string
enum:
- webhook inbox unavailable
- error
'503':
description: Another worker owns an active lease or a signed OpenMercantil checkout cannot yet be resolved to its authoritative account owner; Stripe must retry
headers:
Retry-After:
description: Retry delay in seconds
schema:
type: integer
const: 5
content:
text/plain:
schema:
type: string
enum:
- event already processing
- credits owner unavailable
- subscription owner unavailable
servers:
- url: https://openmercantil.es
description: Production
components:
responses:
BadRequest:
description: Invalid request
headers:
Cache-Control:
$ref: '#/components/headers/NoStoreCacheControl'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
TooManyRequests:
description: Rate limit exceeded
headers:
Retry-After:
description: Seconds to wait before retrying.
schema:
type: integer
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
Unauthorized:
description: Authentication required (no active session)
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
TooManyRequests_2:
description: Rate limit exceeded
headers:
Retry-After:
description: Seconds to wait before retrying.
schema:
type: integer
X-RateLimit-Limit:
description: Maximum requests per window (60 per minute by IP on the public anonymous tier).
schema:
type: integer
X-RateLimit-Remaining:
description: Remaining requests in the current window.
schema:
type: integer
X-RateLimit-Reset:
description: Unix timestamp when the rate-limit counter resets.
schema:
type: integer
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse_2'
BadRequest_2:
description: Invalid request
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse_2'
schemas:
BillingInvoice:
type: object
description: Authenticated user's invoice metadata; URLs point to Stripe-hosted documents.
properties:
id:
type: integer
stripe_invoice_id:
type: string
invoice_number:
type:
- string
- 'null'
amount_paid_cents:
type:
- integer
- 'null'
amount_due_cents:
type:
- integer
- 'null'
subtotal_cents:
type:
- integer
- 'null'
tax_cents:
type:
- integer
- 'null'
tax_rate_pct:
type:
- number
- 'null'
currency:
type:
- string
- 'null'
status:
type:
- string
- 'null'
invoice_pdf:
type:
- string
- 'null'
format: uri
hosted_invoice_url:
type:
- string
- 'null'
format: uri
period_start:
type:
- string
- 'null'
period_end:
type:
- string
- 'null'
paid_at:
type:
- string
- 'null'
created_at:
type: string
additionalProperties:
$ref: '#/components/schemas/JsonValue'
ErrorResponse:
type: object
description: Closed compatibility envelope for public/account errors. Route-specific schemas narrow these fields further where required.
required:
- error
properties:
error:
type: string
minLength: 1
message:
type: string
detail:
type: string
code:
type: string
status:
type:
- integer
- string
projection:
type: string
reason:
type: string
source_catalog_version:
type: string
allowed_parameters:
type: array
uniqueItems: true
items:
type: string
slug:
type: string
key:
type: string
maximum:
type: integer
minimum: 1
parameter:
type: string
fields:
type: array
items:
type: string
max_bytes:
type: integer
minimum: 1
allowed:
type: array
items:
$ref: '#/components/schemas/JsonValue'
valid:
type: array
items:
$ref: '#/components/schemas/JsonValue'
date:
type: string
login_url:
type: string
plan:
type: string
limited_by:
type: string
enum:
- minute
- day
daily_limit:
type: integer
minimum: 1
reset_at:
type: integer
minimum: 1
reset_at_human:
type: string
format: date-time
retry_after_s:
type: integer
minimum: 1
retry_after:
type: integer
minimum: 1
upgrade:
type: string
format: uri
upgrade_url:
type: string
action:
type: string
limit:
type: integer
minimum: 0
remaining:
type: integer
minimum: 0
needed:
type: integer
minimum: 0
shortfall:
type: integer
minimum: 0
ok:
type: boolean
_alias_of:
type: string
additionalProperties: false
JsonValue:
description: A JSON value used only inside explicitly documented extension maps.
oneOf:
- type:
- string
- number
- boolean
- 'null'
- type: array
items:
$ref: '#/components/schemas/JsonValue'
- type: object
additionalProperties:
$ref: '#/components/schemas/JsonValue'
BillingInvoicesResponse:
type: object
required:
- ok
- subscription
- invoices
properties:
ok:
type: boolean
subscription:
anyOf:
- $ref: '#/components/schemas/BillingSubscription'
- type: 'null'
invoices:
type: array
items:
$ref: '#/components/schemas/BillingInvoice'
maxItems: 50
additionalProperties: false
StripeWebhookEventRequest:
type: object
description: Stripe Event envelope. The exact signed raw bytes, not a reserialized object, are used for verification. Provider extension fields remain typed JSON and are not reflected to callers.
required:
- id
- type
- data
properties:
id:
type: string
minLength: 1
description: Provider event id and durable idempotency key.
object:
type: string
const: event
type:
type: string
minLength: 1
created:
type: integer
minimum: 0
livemode:
type: boolean
api_version:
type:
- string
- 'null'
data:
type: object
required:
- object
properties:
object:
type: object
additionalProperties:
$ref: '#/components/schemas/JsonValue'
previous_attributes:
type: object
additionalProperties:
$ref: '#/components/schemas/JsonValue'
additionalProperties:
$ref: '#/components/schemas/JsonValue'
additionalProperties:
$ref: '#/components/schemas/JsonValue'
RequestBodyTooLargeError:
type: object
required:
- error
- max_bytes
properties:
error:
type: string
const: request_body_too_large
max_bytes:
type: integer
minimum: 1
additionalProperties: false
StripeSessionResponse:
type: object
required:
- url
- session_id
properties:
url:
type: string
format: uri
session_id:
type: string
additionalProperties: false
BillingSubscription:
type: object
description: Authenticated user's current subscription metadata.
properties:
id:
type: integer
stripe_subscription_id:
type: string
stripe_customer_id:
type:
- string
- 'null'
plan:
type: string
billing:
type:
- string
- 'null'
amount_cents:
type:
- integer
- 'null'
currency:
type:
- string
- 'null'
status:
type: string
current_period_start:
type:
- string
- 'null'
current_period_end:
type:
- string
- 'null'
cancel_at_period_end:
type:
- integer
- boolean
canceled_at:
type:
- string
- 'null'
created_at:
type: string
updated_at:
type: string
additionalProperties:
$ref: '#/components/schemas/JsonValue'
ErrorResponse_2:
type: object
properties:
error:
type: string
message:
type: string
additionalProperties: true
parameters:
CsrfHeader:
name: X-CSRF-Token
in: header
required: true
description: Fresh token returned by GET /api/v1/user/me for this authenticated session.
schema:
type: string
minLength: 32
maxLength: 256
headers:
NoStoreCacheControl:
description: Error responses must not be stored.
schema:
type: string
const: no-store
securitySchemes:
cookieAuth:
type: apiKey
in: cookie
name: ob_sess
description: Browser session cookie set after login at /mi-cuenta/login. Mutations also require X-CSRF-Token header (obtain via GET /api/v1/user/me).
apiKey:
type: apiKey
in: header
name: X-API-Key
description: Optional opaque omk_* API credential for public GETs. Anonymous access remains valid; a credential with the operation's x-api-credential-scope (or public:read) selects its account quota. Never place credentials in query strings.
bearerAuth:
type: http
scheme: bearer
bearerFormat: opaque omk_* credential
description: 'Optional Authorization: Bearer transport for the same opaque omk_* API credential accepted by X-API-Key. It is not a JWT or OAuth access token.'
sessionCookie:
type: apiKey
in: cookie
name: session
description: Session cookie issued after web sign-in, required only for billing endpoints.
externalDocs:
description: Documentación narrativa con ejemplos en curl/Python/JavaScript
url: https://openmercantil.es/api/documentacion
x-refined-from:
- openmercantil-openapi-1.9.3.json
- openmercantil-openapi.yml