Browse the manual

External client HTTP and MCP reference

MCP and HTTP tools, scoped access, exact monetary strings, invoice approvals and typed failures.

On this page

For: developers connecting an MCP or HTTP client. First set up an owner-granted connection.

The MCP server and HTTP API use the same tools, grants and business actions. Access starts off. A connection needs owner approval.

Transport and access

Use the HTTPS scheme and host/port from APP_URL. Use the addresses shown in Settings > Agent connections.

Send Authorization: Bearer <credential> on every tool request. Cookies and URL parameters do not authenticate calls.

HTTP requests need Accept: application/json. POST and PUT bodies need Content-Type: application/json.

Requests with an Origin header fail. Call the HTTP API from a server or command-line client.

A connection acts as one active, non-guest user. Its grants do not add missing user permissions. Record access remains subject to that user's scope.

Limit Value
Tool request body 256 KiB
Requests per connection 60 per minute
Requests per IP address 60 per minute, shared by clients behind that address
Record list page 1–50 records; default 25
Search text 100 characters
Connection expiry 1–30 days
Invoice lines 1–100
Invoice approval 10 minutes from preparation

Responses use Cache-Control: no-store. Record lists return data and next. Send next as the next request's after cursor.

A null cursor means the last page. Record lists order by ID and disclose no total count.

When demo mode is on, draft writes and invoice issue requests return 403. Permitted reads and previews remain available.

MCP transport

The MCP server address ends in /mcp. It accepts Streamable HTTP POST requests and returns JSON responses.

Use a remote MCP client with JSON responses. This server has no standalone event stream. GET and DELETE return 405 when access is on.

Send one JSON-RPC request or notification per call. JSON arrays and client responses fail. Notifications return 202 with no body.

Authenticate with a pasted connection credential or browser sign-in. Browser sign-in uses the separate owner-controlled switch in Agent connections.

The server supports tool discovery and calls. It does not expose MCP resources or prompts. Use tools/list for the granted tool schemas.

MCP names replace dots with underscores. For example, documents.get becomes documents_get.

For a record-specific MCP call, put id in the arguments. For a writing call, also put idempotency_key in the arguments.

HTTP puts the record ID in the path and the idempotency key in the Idempotency-Key header.

Successful MCP calls return structuredContent with the same data as HTTP. Tool failures set isError and include structured error data.

Authentication and protocol failures can return an HTTP error with a JSON-RPC error. They do not use the tool-result envelope.

Browser sign-in

Browser sign-in uses authorization code flow with PKCE S256. The owner checks the client and return address before allowing access.

Endpoint Purpose
GET /.well-known/oauth-protected-resource/mcp Protected resource metadata; the root variant also works
GET /.well-known/oauth-authorization-server Authorization server metadata
POST /oauth/register Register a public client
GET /oauth/authorize Open the owner consent page
POST /oauth/token Exchange a code or rotate a refresh token
POST /oauth/revoke Revoke a token

Discovery identifies {APP_URL}/mcp as the resource. Send that resource when starting authorization or exchanging and refreshing tokens.

An unknown client or unregistered return address gets an error page. Made with Pepper does not redirect that request.

Public clients can use dynamic registration or a supported HTTPS Client ID Metadata Document. Registered clients need 1–5 return addresses.

Return addresses use HTTPS, or HTTP on localhost, 127.0.0.1 or [::1]. They must match the registered address and contain no fragment.

Authorization codes last 5 minutes and permit one exchange. Access tokens last up to 1 hour, limited by the connection's expiry.

Refresh tokens rotate. Reusing a code or a rotated refresh token revokes its connection. A refresh token never authenticates a tool call.

Registration, token and revocation requests have a 16 KiB body limit. Registration allows 10 requests per minute per IP address.

Token and revocation requests share a limit of 30 per minute per IP address. OAuth errors use error, such as invalid_grant.

Follow the client's sign-in flow for ordinary setup. See Connect through browser sign-in.

Endpoints

GET /api/v1/tools lists granted tools from modules that are on. Each entry gives its HTTP method, path, scope and input schema.

Calls also need the delegated user's current permissions. A listed grant does not establish that every record or action is available.

HTTP method and path MCP tool Scope
GET /api/v1/customers customers_search customers:read
GET /api/v1/documents documents_list invoices:read
GET /api/v1/documents/{id} documents_get invoices:read
POST /api/v1/documents/preview documents_preview invoices:read
POST /api/v1/drafts drafts_create invoices:prepare
PUT /api/v1/drafts/{id} drafts_update invoices:prepare
POST /api/v1/documents/{id}/issue-approvals documents_issue_prepare invoices:execute
GET /api/v1/approvals/{id} approvals_get invoices:execute
POST /api/v1/approvals/{id}/execute documents_issue invoices:execute
GET /api/v1/people people_list people:read
GET /api/v1/people/{id} people_get people:read
GET /api/v1/teams teams_list people:read

Existing connections with individual pilot capabilities keep those capabilities. New connections use the scope groups shown in Permitted access.

Customer and document lists accept search, limit and after. For example, use /api/v1/customers?search=Acme&limit=25.

Customer search returns active permitted IDs and names. Document lists return summaries; an individual read adds public notes, reference and line fields.

Summaries include type, status, number, draft code, customer ID, dates, currency, lock_version, monetary strings, connection ID and href.

A new draft has no document number. Internal notes, customer addresses, bank details, credentials and signed download links stay outside document responses.

People lists accept search, limit, after and optional team_id. Team lists accept search, limit and after.

People reads cover active staff, excluding guests. Responses include name, work email, job title, skills, teams, manager and time zone.

Individual people reads also include weekly working hours. They exclude private contact details, rates, time-off records and sign-in settings.

Team reads include live teams, descriptions, lead IDs and active staff member IDs.

These tools do not send documents, record payments, export files or administer taxes and bank accounts. No item-catalog or full-ledger endpoint exists.

Draft fields

Create and preview require customer_id, currency_code, issue_date, due_date and line_items. Use an active permitted customer and a supported currency.

Dates use YYYY-MM-DD. The due date cannot precede the issue date.

Optional fields are language, notes and reference. Notes allow 5,000 characters; reference allows 100. Language uses a supported code.

Update also requires the current integer lock_version and an editable invoice. Every draft write sends the full header and line set.

Line field Format and limit
description Required text, 1,000 characters
quantity Required positive string, dot decimal, at most 4 decimal places
unit_price Required unsigned minor-unit string, at most 18 digits; no separators or leading zeros except 0
unit Optional text, 20 characters
discount_type Optional percent or amount
discount_value Required when a discount type exists; unsigned string
tax_rate_id Optional ID of an existing active tax rate

For percent, discount_value uses basis points: 1000 means 10%. For amount, it uses the invoice currency's minor units.

Ask a permitted person for the intended tax rate ID. These tools cannot create rates or decide your tax treatment.

Money is exact. A price of "10001" means EUR 100.01, USD 100.01, JPY 10,001 or BHD 10.001.

PHP selects the currency scale and calculates totals. Send money as JSON strings. Do not send floats or numeric money values.

Do not submit totals, status, creator, document type, bank details, legal fields or frozen tax snapshots. Unknown draft fields fail validation.

Example: preview then save

Use a customer ID from the customer endpoint. This example uses no tax rate. Confirm the correct treatment before issuing.

{
  "customer_id": "<permitted-customer-ulid>",
  "currency_code": "USD",
  "issue_date": "2026-10-08",
  "due_date": "2026-11-08",
  "reference": "Northwind October",
  "line_items": [
    {"description": "Website design for October", "quantity": "1.25", "unit_price": "10001"}
  ]
}
  1. POST the JSON to /api/v1/documents/preview.
  2. Check data.grand_total of "12501", or USD 125.01.
  3. POST the JSON to /api/v1/drafts with Idempotency-Key: northwind-october-1.
  4. Open the returned data.href.
  5. Review the saved draft in Made with Pepper.

For an update, PUT the full JSON with the latest lock_version to /api/v1/drafts/{id}. Use a new key for that operation.

MCP uses documents_preview, followed by drafts_create. Add idempotency_key to the draft call's arguments.

Issue after approval

These three tools need invoices:execute and the delegated user's invoices.finalize permission. They handle one invoice at a time.

Execution also rechecks invoices.manage in the issue action. A custom role needs both invoice permissions and access to that invoice.

For version 0, the preparation body is {"lock_version": 0}. Use the draft's actual version.

  1. Read the saved draft and its latest lock_version.
  2. Create a JSON body with that lock_version.
  3. POST the body to /api/v1/documents/{id}/issue-approvals with a new Idempotency-Key.
  4. Show the returned data.review_url to the delegated person.
  5. Retain data.approval_id and data.nonce for execution.
  6. Read /api/v1/approvals/{approval_id} for the decision.
  7. If its status is approved, execute the request within its window.

For execution, POST {"nonce": "<returned-nonce>"} to /api/v1/approvals/{approval_id}/execute. Send a new Idempotency-Key header.

MCP uses documents_issue_prepare, approvals_get and documents_issue. Their id is the document ID for preparation and approval ID afterward.

The preparation response has HTTP 202. It includes the customer, dates, exact totals and expiry. It does not issue anything.

Only the delegated person can click Approve issue or Reject in the app. Another staff member cannot approve through the review link.

Approval lasts 10 minutes from preparation. The decision does not restart the window. approved permits execution; executed confirms issuance.

Execution assigns the next invoice number and freezes the invoice. It sends no email and records no payment.

The result includes the issued document and number. Treat only that result or the issued record as proof of issuance.

A changed draft, expired request, revoked connection or lost permission can prevent execution. Prepare a new request when the previous one is stale.

An identical retry of executed approval returns its stored result. It must use the same connection, nonce and execute key.

Idempotency and conflicts

Each write needs an idempotency key of 1–80 ASCII letters, digits, underscores or hyphens. An identical retry returns its original response.

Keep the JSON values and field order identical. Reusing a key with another payload or target returns 409.

A receipt describes the original write. Read the document for its current status and version. A stale update changes nothing.

Keep your input after a conflict. Read the latest version, review the differences and use a new key. Failed draft transactions store no success receipt.

Executed approvals retain a separate result. A new execute key on the same approval returns approval_used.

Revocation and current permission checks still apply to retries. A retained receipt does not restore lost access.

Errors

HTTP tool errors contain error.code. Validation returns error.fields, a list of field paths. Responses exclude exception messages and credentials.

Status Code Recovery
401 unauthenticated Check expiry, revocation, the delegated user and application URL
403 forbidden Check HTTPS, host/port, Origin, grants, current permissions and demo mode
404 not_found Access is off, or the route/record is missing or outside scope
409 conflict Read the latest version or correct a reused key
409 approval_pending Wait for the delegated person's decision
409 approval_rejected Respect the decision; review the draft before any new request
409 approval_stale Read the draft and prepare a new request
409 approval_used Retry with the original execute key to retrieve the result
410 approval_expired Prepare a new request and obtain a new decision
413 payload_too_large Send a tool body of 256 KiB or less
415 json_required Send the JSON content type
422 validation_failed Correct the named fields; use a new key for changed input
429 rate_limited Wait for the minute window to clear; reduce request frequency
503 unavailable Keep the input; retry with the same key if the earlier write may have succeeded

A hidden record and a missing record produce the same 404. Another connection's approval or an incorrect nonce also returns 404.

After an execution conflict, read the approval and document. Do not assume the client issued anything from a prepared or approved state.

Revocation and retained evidence

Revoking a connection stops its future calls. Turning agent access off revokes all connections. Turning browser sign-in off revokes browser connections.

Expiry, user deactivation, session-revoking permission changes, owner transfer or a changed application URL can also stop access.

Saved drafts, issued originals, connection metadata, request receipts, approval results and audit evidence remain. Revocation cannot erase a client's saved copies.

Read connection setup and privacy boundaries.

Need help with the product?

Contact support