Granite Billing API

Plans and payments (PLAN.md 1.2, Phase 7). A person buys Pro, with its first 30 days free once per person; a workspace buys Business per seat (members above Guest, at least 2) after its 30-day trial. Payments run through Stripe Checkout and Stripe's customer portal. A failed payment keeps the plan for 7 days; after that, or when a subscription ends, the owner is on Free's limits: nothing is deleted, but uploads over the quota are refused. Pass `workspace_id` for a workspace's billing (its owner and admins only).

GitHub OpenAPI

Operations 10

GET /v1/billing Plan, subscription, and storage use #
POST /v1/billing/checkout Buy Pro or Business #
POST /v1/billing/portal Manage payment method, invoices, and cancelation #
POST /v1/billing/app-store Record a Pro purchase made in the iPhone or iPad app #
POST /v1/billing/google-play Record a Pro purchase made in the Android app #
GET /v1/billing/invoices Latest invoices #
GET /v1/billing/payment-methods Saved cards #
POST /v1/billing/payment-methods Add a card #
DELETE /v1/billing/payment-methods/{payment_method_id} Remove a card #
POST /v1/billing/payment-methods/{payment_method_id}/default Pay with this card #

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/granite-md:granite-md-billing-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

granite-md-billing-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Granite Billing API
  version: 1.0.0
  summary: REST API for Granite clients, bots, and integrations.
  description: 'Conventions (apply to every operation; see PLAN.md 3.2 and 3.6):


    - **Base URL** `https://api.granite.md`. All public operations live under `/v1`.

    After Phase 6 ships, `/v1` changes are additive only.

    - **Errors** use RFC 9457 problem details (`application/problem+json`, schema `Problem`).

    The `type` URI is `https://granite.md/problems/`; `code` refines it for programmatic handling.

    Every response carries `X-Request-ID`, also echoed as `request_id` in problems.

    - **Pagination** is cursor based. List operations accept `cursor` and `limit` and return

    `{ "items": [...], "next_cursor": "..." }`. `next_cursor` is absent on the last page.

    Cursors are opaque; clients must not parse or build them.

    - **Idempotency**: non-idempotent `POST` operations accept an `Idempotency-Key` header

    (1-255 printable ASCII). Repeating a request with the same key and body within 24 hours

    returns the original response; the same key with a different body returns `409` with

    code `idempotency_key_reused`.

    - **IDs** are UUIDv7 strings. **Timestamps** are RFC 3339 in UTC.

    - **Authentication**: `Authorization: Bearer ` with a session access token,

    a personal API token (`gr_pat_...`), or an OAuth access token. Scopes are listed per operation.

    - **Rate limits**: each personal API token may make 600 requests a minute. Over the limit

    the API answers `429` with code `token_rate_limited` and a `Retry-After` header (seconds).'
  license:
    name: Proprietary
    identifier: LicenseRef-Proprietary
servers:
- url: https://api.granite.md
  description: Production
- url: http://localhost:8080
  description: Local development
security:
- bearerAuth: []
tags:
- name: Billing
  description: 'Plans and payments (PLAN.md 1.2, Phase 7). A person buys Pro, with its first 30 days free

    once per person; a workspace buys Business per seat (members above Guest, at least 2)

    after its 30-day trial. Payments run through

    Stripe Checkout and Stripe''s customer portal. A failed payment keeps the plan for 7 days;

    after that, or when a subscription ends, the owner is on Free''s limits: nothing is

    deleted, but uploads over the quota are refused. Pass `workspace_id` for a workspace''s

    billing (its owner and admins only).'
paths:
  /v1/billing:
    get:
      operationId: getBilling
      tags:
      - Billing
      summary: Plan, subscription, and storage use
      description: The caller's, or with `workspace_id` the workspace's, which only its owner sees (403 `billing_forbidden`); a workspace has one billing for all its members.
      parameters:
      - $ref: '#/components/parameters/BillingWorkspace'
      responses:
        '200':
          description: Billing state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BillingStatus'
        default:
          $ref: '#/components/responses/Error'
  /v1/billing/checkout:
    post:
      operationId: startCheckout
      tags:
      - Billing
      summary: Buy Pro or Business
      description: 'Returns a Stripe Checkout page to open. Pro is for a person, Business for a workspace

        (`workspace_id`). Pro starts with `pro_trial_days` free (30 for a person who never had

        Pro or a Pro trial on any channel, else none); Business starts after the rest of the trial, with `seats` or the

        members above Guest, whichever is more (at least 2). 409 `subscription_exists` when the

        owner already has an active subscription on any channel (the message says where it is

        managed), or 409 `account_in_workspace` for Pro when the account belongs to a workspace

        (a company account has its workspace''s plan); 503 `billing_unavailable` when payments are not set up.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CheckoutRequest'
      responses:
        '200':
          description: The page to open.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BillingRedirect'
        default:
          $ref: '#/components/responses/Error'
  /v1/billing/portal:
    post:
      operationId: openBillingPortal
      tags:
      - Billing
      summary: Manage payment method, invoices, and cancelation
      description: Returns Stripe's customer portal. 404 `no_billing_account` before the first purchase. A workspace's billing is its owner's (403 `billing_forbidden` otherwise).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BillingOwnerRequest'
      responses:
        '200':
          description: The page to open.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BillingRedirect'
        default:
          $ref: '#/components/responses/Error'
  /v1/billing/app-store:
    post:
      operationId: confirmAppStorePurchase
      tags:
      - Billing
      summary: Record a Pro purchase made in the iPhone or iPad app
      description: 'The app sends the StoreKit 2 signed transaction (JWS) after a purchase or restore; it

        must carry the account''s ID as `appAccountToken`. 403 `purchase_not_yours` when it

        belongs to another account (or is already held by one); 400 `invalid_purchase` when the

        signature does not lead to Apple. Store notifications keep it current afterwards.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - signed_transaction
              additionalProperties: false
              properties:
                signed_transaction:
                  type: string
                  minLength: 1
                  maxLength: 20000
      responses:
        '200':
          description: Billing state after the purchase.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BillingStatus'
        default:
          $ref: '#/components/responses/Error'
  /v1/billing/google-play:
    post:
      operationId: confirmGooglePlayPurchase
      tags:
      - Billing
      summary: Record a Pro purchase made in the Android app
      description: 'The app sends the purchase token; it must have been bought with the account''s ID as the

        obfuscated account ID. The server reads it from Google Play and acknowledges it.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - purchase_token
              additionalProperties: false
              properties:
                purchase_token:
                  type: string
                  minLength: 1
                  maxLength: 4096
      responses:
        '200':
          description: Billing state after the purchase.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BillingStatus'
        default:
          $ref: '#/components/responses/Error'
  /v1/billing/invoices:
    get:
      operationId: listInvoices
      tags:
      - Billing
      summary: Latest invoices
      parameters:
      - $ref: '#/components/parameters/BillingWorkspace'
      responses:
        '200':
          description: Up to 24 invoices, newest first.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InvoiceList'
        default:
          $ref: '#/components/responses/Error'
  /v1/billing/payment-methods:
    get:
      operationId: listPaymentMethods
      tags:
      - Billing
      summary: Saved cards
      description: The cards saved with Stripe, the default first. None before the first purchase or card.
      parameters:
      - $ref: '#/components/parameters/BillingWorkspace'
      responses:
        '200':
          description: Saved cards.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PaymentMethodList'
        default:
          $ref: '#/components/responses/Error'
    post:
      operationId: addPaymentMethod
      tags:
      - Billing
      summary: Add a card
      description: 'Returns Stripe''s hosted page for saving a card (Checkout in setup mode); nothing is

        charged. The card becomes the default when there was none. 503 `billing_unavailable`

        when payments are not set up; 403 `billing_forbidden` for a workspace you do not own.'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BillingOwnerRequest'
      responses:
        '200':
          description: The page to open.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BillingRedirect'
        default:
          $ref: '#/components/responses/Error'
  /v1/billing/payment-methods/{payment_method_id}:
    parameters:
    - $ref: '#/components/parameters/PaymentMethodID'
    delete:
      operationId: removePaymentMethod
      tags:
      - Billing
      summary: Remove a card
      description: '409 `card_needed` for the default card while a subscription is paid with it and no other

        card is saved; 404 `no_such_card` for a card that is not the owner''s.'
      parameters:
      - $ref: '#/components/parameters/BillingWorkspace'
      responses:
        '204':
          description: Removed.
        default:
          $ref: '#/components/responses/Error'
  /v1/billing/payment-methods/{payment_method_id}/default:
    parameters:
    - $ref: '#/components/parameters/PaymentMethodID'
    post:
      operationId: setDefaultPaymentMethod
      tags:
      - Billing
      summary: Pay with this card
      description: Makes the card the default for future invoices, including the current subscription's.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BillingOwnerRequest'
      responses:
        '204':
          description: It is the default now.
        default:
          $ref: '#/components/responses/Error'
components:
  schemas:
    Company:
      type: object
      description: 'The workspace a company account belongs to. Its plan is the person''s plan; there is

        nothing for the person to buy.

        '
      required:
      - workspace_id
      - workspace_name
      properties:
        workspace_id:
          type: string
          format: uuid
        workspace_name:
          type: string
    BillingRedirect:
      type: object
      required:
      - url
      properties:
        url:
          type: string
          format: uri
    PaymentMethodList:
      type: object
      required:
      - items
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/PaymentMethod'
    PaymentMethod:
      type: object
      required:
      - id
      - brand
      - last4
      - exp_month
      - exp_year
      - default
      properties:
        id:
          type: string
        brand:
          type: string
          description: visa, mastercard, amex, ... as Stripe names it
        last4:
          type: string
        exp_month:
          type: integer
          format: int32
        exp_year:
          type: integer
          format: int32
        default:
          type: boolean
          description: Future invoices are paid with this card.
    BillingStatus:
      type: object
      required:
      - plan
      - plan_name
      - monthly_price_cents
      - per_seat
      - status
      - seats
      - storage_bytes
      - storage_limit_bytes
      - purchasable
      - manageable
      properties:
        plan:
          type: string
          description: free, pro, or business
        plan_name:
          type: string
        monthly_price_cents:
          type: integer
          format: int64
          description: Per seat for per-seat plans.
        per_seat:
          type: boolean
        status:
          type: string
          enum:
          - free
          - trial
          - active
          - past_due
          - lapsed
          description: '`trial`: a workspace''s free 30 days. `past_due`: a payment failed; the plan works

            until `grace_until`. `lapsed`: a workspace whose trial ended without a subscription.

            '
        trial_ends_at:
          type: string
          format: date-time
        grace_until:
          type: string
          format: date-time
        seats:
          type: integer
          format: int64
        storage_bytes:
          type: integer
          format: int64
        storage_limit_bytes:
          type: integer
          format: int64
        purchasable:
          type: boolean
          description: Payments are set up on this server.
        manageable:
          type: boolean
          description: There is a billing account (portal and invoices).
        pro_trial_days:
          type: integer
          format: int64
          description: 'Free days a Pro checkout starts with: 30 for a person who never had Pro or a Pro

            trial on any channel (web, App Store, Google Play), else 0. Always 0 for a workspace.

            '
        subscription:
          $ref: '#/components/schemas/SubscriptionSummary'
        company:
          $ref: '#/components/schemas/Company'
    Problem:
      type: object
      description: RFC 9457 problem details.
      required:
      - type
      - title
      - status
      properties:
        type:
          type: string
          format: uri
          examples:
          - https://granite.md/problems/conflict
        title:
          type: string
        status:
          type: integer
          format: int32
        detail:
          type: string
        instance:
          type: string
        code:
          type: string
          description: Machine-readable refinement of `type`, for example `version_conflict`.
        request_id:
          type: string
        errors:
          type: array
          description: Per-field validation errors.
          items:
            $ref: '#/components/schemas/FieldError'
    Invoice:
      type: object
      required:
      - id
      - number
      - created_at
      - total_cents
      - currency
      - status
      - url
      properties:
        id:
          type: string
        number:
          type: string
        created_at:
          type: string
          format: date-time
        total_cents:
          type: integer
          format: int64
        currency:
          type: string
        status:
          type: string
        url:
          type: string
          description: Stripe's page for the invoice.
        pdf_url:
          type: string
    CheckoutRequest:
      type: object
      required:
      - plan
      additionalProperties: false
      properties:
        plan:
          type: string
          enum:
          - pro
          - business
        workspace_id:
          type: string
          format: uuid
        seats:
          type: integer
          format: int64
          minimum: 2
          maximum: 10000
    InvoiceList:
      type: object
      required:
      - items
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/Invoice'
    BillingOwnerRequest:
      type: object
      additionalProperties: false
      properties:
        workspace_id:
          type: string
          format: uuid
    FieldError:
      type: object
      required:
      - field
      - detail
      properties:
        field:
          type: string
          description: JSON path of the invalid field, for example `ops[2].path`.
        detail:
          type: string
    SubscriptionSummary:
      type: object
      required:
      - provider
      - status
      - seats
      - cancel_at_period_end
      properties:
        provider:
          type: string
          enum:
          - stripe
          - app_store
          - google_play
          - internal
          description: Where it is paid; `internal` is granted by Granite and never billed.
        status:
          type: string
        seats:
          type: integer
          format: int32
        current_period_end:
          type: string
          format: date-time
        cancel_at_period_end:
          type: boolean
  headers:
    RequestID:
      description: Identifier of this request, for support and log correlation.
      schema:
        type: string
  parameters:
    BillingWorkspace:
      name: workspace_id
      in: query
      required: false
      description: A workspace you own; omit for your own billing.
      schema:
        type: string
        format: uuid
    PaymentMethodID:
      name: payment_method_id
      in: path
      required: true
      description: Stripe's ID of the card (pm_...).
      schema:
        type: string
        minLength: 1
        maxLength: 255
  responses:
    Error:
      description: Error response.
      headers:
        X-Request-ID:
          $ref: '#/components/headers/RequestID'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: Session access token, personal API token, or OAuth access token.