Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
description: 'Company-scoped REST API for invoicing, Peppol and loyalty.
**Getting started.** Create a key in Company → API keys, grant only the needed
scopes, and call `GET /api/v1/me`. Send `Authorization: Bearer ik_live_...` or
`X-Api-Key: ik_live_...`. The key identifies the company; no company header is
needed. Session cookies do not authenticate business endpoints. This OpenAPI
document is public metadata and contains no company or API-key values.
**Scopes.** Every operation lists all required scopes. `resource:write` implies
the registered `resource:read` scope. `peppol:send` and `fidelity:points:write`
are standalone. Invoice prepayments additionally require `payments:write`.
**Retry safety.** Operations that accept `Idempotency-Key` replay successful
responses for 24 hours with `Idempotent-Replay: true`. Reusing a key for different
contents returns 409 `IDEMPOTENCY_KEY_REUSED`; in-flight retries return 409
`IDEMPOTENT_REQUEST_IN_PROGRESS`. Header support is shown only where implemented.
Document and financial movement `external_id` values provide durable recovery
within the key''s integration namespace, including after key rotation. Loyalty
movements also accept a body `idempotency_key` stored on the ledger.
**Money and settlement.** Document totals are JSON numbers. Financial amounts
and settlement summaries are exact decimal strings; send decimal strings for
payments. Check `/me` capabilities for currency precision and supported payment
methods. Payment/refund operations record already-confirmed money movements.
**Lists.** Paginated operations accept `limit` (1–100, default 25) and `offset`.
Loyalty members and ledger round to 25/50/100-row pages. Scope, reward and card
lists and financial history do not paginate.
**Rate limits.** Authenticated calls are limited per key, per minute. Calls that
reach the limiter carry `X-RateLimit-Limit`, `X-RateLimit-Remaining` and
`X-RateLimit-Reset` (seconds until reset); 429 also carries `Retry-After`.
**Errors.** JSON `{"code": "...", "message": "...", "details": {...}}`.
`details` is optional. Branch on stable `code`, since messages may change.
Unknown request fields are rejected on clients, documents, Peppol sends, members,
points, rewards and cards. Program settings are normalized separately.
**Peppol.** Structured fields build UBL XML. `POST /api/v1/peppol/send` creates
an invoice and transmits it; supplying `pdf` uses your exact rendering as its
attachment. Existing invoices and credit notes have dedicated send routes.
Approval and monthly quota apply. Archive the exchanged XML and original PDF;
`view=current` renders the current settlement and can differ from issued bytes.
For an uncertain transmission, read its status and reconcile the outcome before
attempting another send.'
title: OrdoGrid Public Clients API
version: 1.4.0
servers:
- url: /
security:
- ApiKeyAuth: []
- ApiKeyHeader: []
tags:
- description: Company-scoped customer records used as document recipients.
name: Clients
paths:
/api/v1/clients:
get:
description: 'Requires all scopes: `clients:read`.'
operationId: get_api_v1_clients_list_clients
parameters:
- description: Maximum rows per page.
in: query
name: limit
required: false
schema:
default: 25
maximum: 100
minimum: 1
type: integer
- description: Zero-based row offset.
in: query
name: offset
required: false
schema:
default: 0
maximum: 1000000
minimum: 0
type: integer
- description: Client status.
in: query
name: status
required: false
schema:
enum:
- active
- archived
type: string
- description: Exact email match.
in: query
name: email
required: false
schema:
type: string
- description: Case-insensitive name prefix.
in: query
name: q
required: false
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/ClientPage'
description: Success
headers:
X-RateLimit-Limit:
description: Requests permitted per minute for this key.
schema:
type: integer
X-RateLimit-Remaining:
description: Requests remaining in this window.
schema:
type: integer
X-RateLimit-Reset:
description: Seconds until the rate limit window resets.
schema:
type: integer
'400':
content:
application/json:
example:
code: VALIDATION_ERROR
message: Invalid request
schema:
$ref: '#/components/schemas/Error'
description: 'Invalid request. Possible code values: VALIDATION_ERROR, UNKNOWN_FIELD, INVALID_VAT_NUMBER.'
'401':
content:
application/json:
example:
code: API_KEY_REQUIRED
message: Missing, malformed, revoked or expired API key
schema:
$ref: '#/components/schemas/Error'
description: 'Missing, malformed, revoked or expired API key. Possible code values: API_KEY_REQUIRED, INVALID_API_KEY, API_KEY_REVOKED, API_KEY_EXPIRED.'
'403':
content:
application/json:
example:
code: INSUFFICIENT_SCOPE
message: Key scope, company or subscription refused the call
schema:
$ref: '#/components/schemas/Error'
description: 'Key scope, company or subscription refused the call. Possible code values: INSUFFICIENT_SCOPE, COMPANY_SUSPENDED, PEPPOL_APPROVAL_REQUIRED, PEPPOL_LIMIT_REACHED, SUBSCRIPTION_REQUIRED, FIDELITY_NOT_ENABLED.'
'404':
content:
application/json:
example:
code: NOT_FOUND
message: Not found in this company
schema:
$ref: '#/components/schemas/Error'
description: 'Not found in this company. Possible code values: NOT_FOUND, CLIENT_NOT_FOUND, PDF_NOT_AVAILABLE, XML_NOT_AVAILABLE, FIDELITY_MEMBER_NOT_FOUND, REWARD_NOT_FOUND.'
'422':
content:
application/json:
example:
code: BUSINESS_RULE_ERROR
message: Business rule refused the operation
schema:
$ref: '#/components/schemas/Error'
description: 'Business rule refused the operation. Possible code values: BUSINESS_RULE_ERROR, ILLEGAL_STATUS_TRANSITION, INSUFFICIENT_POINTS.'
'429':
content:
application/json:
example:
code: RATE_LIMITED
message: API key rate limit exceeded
schema:
$ref: '#/components/schemas/Error'
description: 'API key rate limit exceeded. Possible code values: RATE_LIMITED.'
headers:
Retry-After:
description: Seconds until retry is allowed.
schema:
type: integer
'500':
content:
application/json:
example:
code: INTERNAL_ERROR
message: Unexpected server error
schema:
$ref: '#/components/schemas/Error'
description: 'Unexpected server error. Possible code values: INTERNAL_ERROR.'
security:
- ApiKeyAuth: []
- ApiKeyHeader: []
summary: List clients
tags:
- Clients
x-required-scopes:
- clients:read
post:
description: 'Requires all scopes: `clients:write`.'
operationId: post_api_v1_clients_create_client
parameters:
- description: 'Optional client-generated retry key. Successful responses replay for 24 hours with Idempotent-Replay: true. A different request returns 409.'
example: erp-invoice-1001-create
in: header
name: Idempotency-Key
required: false
schema:
maxLength: 255
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ClientCreate'
required: true
responses:
'201':
content:
application/json:
schema:
$ref: '#/components/schemas/Client'
description: Created
headers:
Idempotent-Replay:
description: true when replaying a previously successful result.
schema:
type: boolean
X-RateLimit-Limit:
description: Requests permitted per minute for this key.
schema:
type: integer
X-RateLimit-Remaining:
description: Requests remaining in this window.
schema:
type: integer
X-RateLimit-Reset:
description: Seconds until the rate limit window resets.
schema:
type: integer
'400':
content:
application/json:
example:
code: VALIDATION_ERROR
message: Invalid request
schema:
$ref: '#/components/schemas/Error'
description: 'Invalid request. Possible code values: VALIDATION_ERROR, UNKNOWN_FIELD, INVALID_VAT_NUMBER.'
'401':
content:
application/json:
example:
code: API_KEY_REQUIRED
message: Missing, malformed, revoked or expired API key
schema:
$ref: '#/components/schemas/Error'
description: 'Missing, malformed, revoked or expired API key. Possible code values: API_KEY_REQUIRED, INVALID_API_KEY, API_KEY_REVOKED, API_KEY_EXPIRED.'
'403':
content:
application/json:
example:
code: INSUFFICIENT_SCOPE
message: Key scope, company or subscription refused the call
schema:
$ref: '#/components/schemas/Error'
description: 'Key scope, company or subscription refused the call. Possible code values: INSUFFICIENT_SCOPE, COMPANY_SUSPENDED, PEPPOL_APPROVAL_REQUIRED, PEPPOL_LIMIT_REACHED, SUBSCRIPTION_REQUIRED, FIDELITY_NOT_ENABLED.'
'404':
content:
application/json:
example:
code: NOT_FOUND
message: Not found in this company
schema:
$ref: '#/components/schemas/Error'
description: 'Not found in this company. Possible code values: NOT_FOUND, CLIENT_NOT_FOUND, PDF_NOT_AVAILABLE, XML_NOT_AVAILABLE, FIDELITY_MEMBER_NOT_FOUND, REWARD_NOT_FOUND.'
'409':
content:
application/json:
example:
code: IDEMPOTENCY_KEY_REUSED
message: Retry or identity conflict
schema:
$ref: '#/components/schemas/Error'
description: 'Retry or identity conflict. Possible code values: IDEMPOTENCY_KEY_REUSED, IDEMPOTENT_REQUEST_IN_PROGRESS, EXTERNAL_ID_REUSED, EXTERNAL_OPERATION_IN_PROGRESS.'
'422':
content:
application/json:
example:
code: BUSINESS_RULE_ERROR
message: Business rule refused the operation
schema:
$ref: '#/components/schemas/Error'
description: 'Business rule refused the operation. Possible code values: BUSINESS_RULE_ERROR, ILLEGAL_STATUS_TRANSITION, INSUFFICIENT_POINTS.'
'429':
content:
application/json:
example:
code: RATE_LIMITED
message: API key rate limit exceeded
schema:
$ref: '#/components/schemas/Error'
description: 'API key rate limit exceeded. Possible code values: RATE_LIMITED.'
headers:
Retry-After:
description: Seconds until retry is allowed.
schema:
type: integer
'500':
content:
application/json:
example:
code: INTERNAL_ERROR
message: Unexpected server error
schema:
$ref: '#/components/schemas/Error'
description: 'Unexpected server error. Possible code values: INTERNAL_ERROR.'
security:
- ApiKeyAuth: []
- ApiKeyHeader: []
summary: Create a client
tags:
- Clients
x-required-scopes:
- clients:write
/api/v1/clients/{client_id}:
get:
description: 'Requires all scopes: `clients:read`.'
operationId: get_api_v1_clients_get_client
parameters:
- in: path
name: client_id
required: true
schema:
example: 507f1f77bcf86cd799439011
pattern: ^[a-fA-F0-9]{24}$
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Client'
description: Success
headers:
X-RateLimit-Limit:
description: Requests permitted per minute for this key.
schema:
type: integer
X-RateLimit-Remaining:
description: Requests remaining in this window.
schema:
type: integer
X-RateLimit-Reset:
description: Seconds until the rate limit window resets.
schema:
type: integer
'400':
content:
application/json:
example:
code: VALIDATION_ERROR
message: Invalid request
schema:
$ref: '#/components/schemas/Error'
description: 'Invalid request. Possible code values: VALIDATION_ERROR, UNKNOWN_FIELD, INVALID_VAT_NUMBER.'
'401':
content:
application/json:
example:
code: API_KEY_REQUIRED
message: Missing, malformed, revoked or expired API key
schema:
$ref: '#/components/schemas/Error'
description: 'Missing, malformed, revoked or expired API key. Possible code values: API_KEY_REQUIRED, INVALID_API_KEY, API_KEY_REVOKED, API_KEY_EXPIRED.'
'403':
content:
application/json:
example:
code: INSUFFICIENT_SCOPE
message: Key scope, company or subscription refused the call
schema:
$ref: '#/components/schemas/Error'
description: 'Key scope, company or subscription refused the call. Possible code values: INSUFFICIENT_SCOPE, COMPANY_SUSPENDED, PEPPOL_APPROVAL_REQUIRED, PEPPOL_LIMIT_REACHED, SUBSCRIPTION_REQUIRED, FIDELITY_NOT_ENABLED.'
'404':
content:
application/json:
example:
code: NOT_FOUND
message: Not found in this company
schema:
$ref: '#/components/schemas/Error'
description: 'Not found in this company. Possible code values: NOT_FOUND, CLIENT_NOT_FOUND, PDF_NOT_AVAILABLE, XML_NOT_AVAILABLE, FIDELITY_MEMBER_NOT_FOUND, REWARD_NOT_FOUND.'
'422':
content:
application/json:
example:
code: BUSINESS_RULE_ERROR
message: Business rule refused the operation
schema:
$ref: '#/components/schemas/Error'
description: 'Business rule refused the operation. Possible code values: BUSINESS_RULE_ERROR, ILLEGAL_STATUS_TRANSITION, INSUFFICIENT_POINTS.'
'429':
content:
application/json:
example:
code: RATE_LIMITED
message: API key rate limit exceeded
schema:
$ref: '#/components/schemas/Error'
description: 'API key rate limit exceeded. Possible code values: RATE_LIMITED.'
headers:
Retry-After:
description: Seconds until retry is allowed.
schema:
type: integer
'500':
content:
application/json:
example:
code: INTERNAL_ERROR
message: Unexpected server error
schema:
$ref: '#/components/schemas/Error'
description: 'Unexpected server error. Possible code values: INTERNAL_ERROR.'
security:
- ApiKeyAuth: []
- ApiKeyHeader: []
summary: Retrieve a client
tags:
- Clients
x-required-scopes:
- clients:read
patch:
description: 'Requires all scopes: `clients:write`.'
operationId: patch_api_v1_clients_update_client
parameters:
- in: path
name: client_id
required: true
schema:
example: 507f1f77bcf86cd799439011
pattern: ^[a-fA-F0-9]{24}$
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ClientUpdate'
required: true
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Client'
description: Success
headers:
X-RateLimit-Limit:
description: Requests permitted per minute for this key.
schema:
type: integer
X-RateLimit-Remaining:
description: Requests remaining in this window.
schema:
type: integer
X-RateLimit-Reset:
description: Seconds until the rate limit window resets.
schema:
type: integer
'400':
content:
application/json:
example:
code: VALIDATION_ERROR
message: Invalid request
schema:
$ref: '#/components/schemas/Error'
description: 'Invalid request. Possible code values: VALIDATION_ERROR, UNKNOWN_FIELD, INVALID_VAT_NUMBER.'
'401':
content:
application/json:
example:
code: API_KEY_REQUIRED
message: Missing, malformed, revoked or expired API key
schema:
$ref: '#/components/schemas/Error'
description: 'Missing, malformed, revoked or expired API key. Possible code values: API_KEY_REQUIRED, INVALID_API_KEY, API_KEY_REVOKED, API_KEY_EXPIRED.'
'403':
content:
application/json:
example:
code: INSUFFICIENT_SCOPE
message: Key scope, company or subscription refused the call
schema:
$ref: '#/components/schemas/Error'
description: 'Key scope, company or subscription refused the call. Possible code values: INSUFFICIENT_SCOPE, COMPANY_SUSPENDED, PEPPOL_APPROVAL_REQUIRED, PEPPOL_LIMIT_REACHED, SUBSCRIPTION_REQUIRED, FIDELITY_NOT_ENABLED.'
'404':
content:
application/json:
example:
code: NOT_FOUND
message: Not found in this company
schema:
$ref: '#/components/schemas/Error'
description: 'Not found in this company. Possible code values: NOT_FOUND, CLIENT_NOT_FOUND, PDF_NOT_AVAILABLE, XML_NOT_AVAILABLE, FIDELITY_MEMBER_NOT_FOUND, REWARD_NOT_FOUND.'
'422':
content:
application/json:
example:
code: BUSINESS_RULE_ERROR
message: Business rule refused the operation
schema:
$ref: '#/components/schemas/Error'
description: 'Business rule refused the operation. Possible code values: BUSINESS_RULE_ERROR, ILLEGAL_STATUS_TRANSITION, INSUFFICIENT_POINTS.'
'429':
content:
application/json:
example:
code: RATE_LIMITED
message: API key rate limit exceeded
schema:
$ref: '#/components/schemas/Error'
description: 'API key rate limit exceeded. Possible code values: RATE_LIMITED.'
headers:
Retry-After:
description: Seconds until retry is allowed.
schema:
type: integer
'500':
content:
application/json:
example:
code: INTERNAL_ERROR
message: Unexpected server error
schema:
$ref: '#/components/schemas/Error'
description: 'Unexpected server error. Possible code values: INTERNAL_ERROR.'
security:
- ApiKeyAuth: []
- ApiKeyHeader: []
summary: Update a client
tags:
- Clients
x-required-scopes:
- clients:write
components:
schemas:
ClientUpdate:
additionalProperties: false
description: 'Partial update: a field left out keeps its stored value. null, or "" for a text field, removes email, phone, website, contact_person, notes, vat_number, quote_slug or billing_address, returns purchase_order_format to the default "PO: {input}" and sets invoice_requires_po to false. Required values cannot be removed: null or blank type, status, address, a company''s name or an individual''s first_name or last_name returns 400 REQUIRED_FIELD with the fields in details.fields, as does switching type without the new type''s names. Setting type to individual also removes the VAT number unless the same request sends one. Documents already created keep the client details they were created with.'
example:
notes: Prefers email
properties:
address:
$ref: '#/components/schemas/AddressInput'
billing_address:
anyOf:
- $ref: '#/components/schemas/AddressInput'
- type: 'null'
contact_person:
type:
- string
- 'null'
email:
type:
- string
- 'null'
first_name:
minLength: 1
type: string
invoice_requires_po:
description: null means false.
type:
- boolean
- 'null'
last_name:
minLength: 1
type: string
name:
minLength: 1
type: string
notes:
type:
- string
- 'null'
phone:
type:
- string
- 'null'
purchase_order_format:
type:
- string
- 'null'
quote_slug:
type:
- string
- 'null'
status:
enum:
- active
- archived
type: string
type:
enum:
- company
- individual
type: string
vat_number:
type:
- string
- 'null'
website:
type:
- string
- 'null'
type: object
Address:
properties:
city:
type:
- string
- 'null'
country:
type:
- string
- 'null'
country_code:
type:
- string
- 'null'
postal_code:
type:
- string
- 'null'
street:
type:
- string
- 'null'
required:
- street
- city
- postal_code
- country
- country_code
type: object
Client:
properties:
address:
anyOf:
- $ref: '#/components/schemas/Address'
- type: 'null'
email:
type:
- string
- 'null'
id:
type:
- string
- 'null'
name:
type:
- string
- 'null'
object:
const: client
phone:
type:
- string
- 'null'
status:
type:
- string
- 'null'
type:
type:
- string
- 'null'
vat_number:
type:
- string
- 'null'
required:
- id
- object
- type
- name
- email
- phone
- vat_number
- status
- address
type: object
AddressInput:
additionalProperties: false
example:
city: Brussels
country: BE
postal_code: '1000'
street: Rue du Commerce 12
properties:
city:
minLength: 1
type: string
country:
description: ISO 3166-1 alpha-2 code, e.g. BE. A country name in French, Dutch, English or German is accepted and stored as its code; an unknown country is a 400 INVALID_COUNTRY.
minLength: 1
type: string
postal_code:
minLength: 1
type: string
street:
minLength: 1
type: string
required:
- street
- city
- postal_code
- country
type: object
ClientPage:
properties:
data:
items:
$ref: '#/components/schemas/Client'
type: array
object:
const: list
pagination:
properties:
has_more:
type: boolean
limit:
type: integer
offset:
type: integer
total:
type: integer
type: object
required:
- object
- data
- pagination
type: object
Error:
properties:
code:
description: Stable machine-readable code. Branch on this.
type: string
details:
description: Context, e.g. which fields were unexpected.
type: object
message:
description: Human-readable prose. May change.
type: string
required:
- code
- message
type: object
ClientCreate:
additionalProperties: false
allOf:
- else:
properties:
name:
minLength: 1
type: string
required:
- name
if:
properties:
type:
const: individual
then:
properties:
first_name:
minLength: 1
type: string
last_name:
minLength: 1
type: string
required:
- first_name
- last_name
description: Company clients require name; individual clients require first_name and last_name. A complete address is required.
example:
address:
city: Brussels
country: BE
postal_code: '1000'
street: Rue du Commerce 12
name: Example Customer
type: company
properties:
address:
$ref: '#/components/schemas/AddressInput'
billing_address:
anyOf:
- $ref: '#/components/schemas/AddressInput'
- type: 'null'
contact_person:
type:
- string
- 'null'
email:
type:
- string
- 'null'
first_name:
type:
- string
- 'null'
invoice_requires_po:
description: null means false.
type:
- boolean
- 'null'
last_name:
type:
- string
- 'null'
name:
type:
- string
- 'null'
notes:
type:
- string
- 'null'
phone:
type:
- string
- 'null'
purchase_order_format:
type:
- string
- 'null'
quote_slug:
type:
- string
- 'null'
status:
enum:
- active
- archived
type: string
type:
enum:
- company
- individual
type: string
vat_number:
type:
- string
- 'null'
website:
type:
- string
- 'null'
required:
- type
- address
type: object
securitySchemes:
ApiKeyAuth:
bearerFormat: API key
description: Company-scoped key prefixed ik_live_. Create it in Company → API keys. X-Api-Key is also accepted.
scheme: bearer
type: http
ApiKeyHeader:
description: 'Alternative to Authorization: Bearer.'
in: header
name: X-Api-Key
type: apiKey