Invoices

Invoice types
TypeWhat it isType-specific rules
PAYMEA one-off invoice for a fixed amount.amount is required. Cancellation is refused once a payment has started.
GOODS_SERVICESA one-off invoice with line items.items[] is required and orderId is optional. Cancellation is refused once a payment has started.
DONATIONA standing donation with no fixed amount.title applies to this type only, and the amount on the detail card is always null for a donation, whatever it has collected — what came in is reported by other fields, see Amounts and base currency. It can be cancelled whatever it has already received.
SUBSCRIPTIONA recurring payment.amount is required, and the plan is created in the dashboard only. Cancelling it does not depend on payments and is final; once cancelled, it can be deleted, provided none of its period invoices has a payment attempt or receipt.
Search branches
What you typedBranch addedHow it matches
A full UUIDidExact equality. An id is never matched partially.
Digits, optionally with a decimal partbaseAmountExact equality after conversion to minor units. "100" finds an invoice whose baseAmount is 10000, and "1" does not find "100" — there is no substring search on money.
Anything at alldescriptionCase-insensitive substring match. This branch is always present.
Ownership check results
What you sentResponse
A string that is not a valid UUID400 VALIDATION_ERROR
A valid UUID no invoice has404 BUSINESS_INVOICE_NOT_FOUND
An invoice belonging to another merchant403 FORBIDDEN
An invoice you have already deleted404 BUSINESS_INVOICE_NOT_FOUND

Create an invoice

POST/invoices

Auth: JWT or API key signature

A single route creates invoices of every type — there is no per-type endpoint. type is the discriminator that decides which of the remaining fields apply.

Body parameters
NameTypeRequiredDefaultDescription
typeenumRequiredOne of PAYME (one-off invoice for a fixed amount), GOODS_SERVICES (one-off invoice with line items), DONATION (standing donation) or SUBSCRIPTION (recurring payment).
descriptionstringOptionalFree-form text carried on the invoice. The list search matches against it.
metadataobjectOptionalArbitrary JSON object stored with the invoice.
allowedCryptoCurrenciesstring[] (coin codes)OptionalThe subset of the coins you accept that this invoice may be paid in, for example ["USDT_TRX", "BTC"]. Omit it and the full merchant coin pool is frozen into the invoice at creation.
amountstringRequired for PAYME and SUBSCRIPTIONInvoice amount, as a decimal string. Policy limits are checked against the USD equivalent.
items[]{ title, quantity, unitPrice, productImageId? }Required for GOODS_SERVICESLine items. productImageId is an optional UUID of an image from your merchant catalog; the image is snapshotted into the invoice and read back as imageUrl.
orderIdstringOptionalYour own order reference. GOODS_SERVICES only.
titlestringOptionalDonation title. DONATION only.
POST/invoices
curl -X POST 'https://dev-app.tedr.com/api/invoices' \
  -H 'Authorization: Bearer YOUR_SESSION_JWT' \
  -H 'Content-Type: application/json' \
  -d '{
    "type": "GOODS_SERVICES",
    "orderId": "ORD-2041",
    "items": [
      { "title": "Consulting hour", "quantity": 3, "unitPrice": "120.00" }
    ]
  }'

Response: 201

{
  "invoice": {
    "businessInvoiceId": "6f1c1e2c-9b4c-4a2e-9f8b-0d5a1b2c3d4e",
    "type": "GOODS_SERVICES",
    "status": "ACTIVE",
    "businessInvoiceUrl": "https://pay.tedr.com/pay/PbQ7x8kR2mT4",
    "createdAt": "2026-07-30T10:15:00.000Z",
    "expiresAt": "2026-08-29T10:15:00.000Z"
  }
}
Errors
CodeStatusWhen
VALIDATION_ERROR400The body failed validation: an unknown field, an unknown type, or an amount of zero or less.
PRODUCT_IMAGE_NOT_FOUND400productImageId is unknown, belongs to another merchant, or was removed from the catalog.
UNAUTHORIZED401The request carries no valid JWT.

The 201 body is the minimal envelope — it carries no amount. Read the invoice by id for the full card. Validation is strict: an unknown field in the body or an unknown type is a 400. Creation writes a single row — no payment currency is chosen and no payment provider is called, and the merchant settings in force at that moment are frozen into the invoice. A server-to-server caller signs this request with an API key instead of a session JWT: see Authentication and API keys.

List invoices

GET/invoices

Auth: JWT or API key signature

Lists your invoices with filters, search and keyset pagination. Soft-deleted invoices never appear, whatever the filters.

Query parameters
NameTypeRequiredDefaultDescription
typeenum arrayOptionalIN filter over the invoice types. Repeat the key: ?type=PAYME&type=DONATION.
displayStatusenum arrayOptionalIN filter over the invoice display statuses. The set of accepted values is not published in this version of the reference — see the note below this table.
searchstring, 3–100 charactersOptionalOne OR group over id, amount and description. A full UUID matches an id exactly; a 123 or 123.45 query matches the amount exactly, converted to minor units, so "100" does not find "1000"; description is always matched case-insensitively as a substring.
periodFromstring (ISO 8601)OptionalInclusive lower bound on createdAt.
periodTostring (ISO 8601)OptionalInclusive upper bound on createdAt. It is compared as an instant, so periodTo=2026-07-01 cuts off everything after midnight UTC — send the end of the day yourself.
cursorstringOptionalPosition token taken from nextCursor. Omit it for the first page.
limitinteger, 1–100Optional20Page size.
sortBy'createdAt'OptionalcreatedAtThe only accepted value.
sortOrder'asc' | 'desc'OptionaldescDirection of both sort keys.
GET/invoices
curl -G 'https://dev-app.tedr.com/api/invoices' \
  -H 'Authorization: Bearer YOUR_SESSION_JWT' \
  -d 'type=PAYME' \
  -d 'type=DONATION' \
  -d 'limit=20'

Response: 200

{
  "invoices": [
    {
      "businessInvoiceId": "6f1c1e2c-9b4c-4a2e-9f8b-0d5a1b2c3d4e",
      "businessInvoiceUrl": "https://pay.tedr.com/pay/PbQ7x8kR2mT4",
      "status": "ACTIVE",
      "type": "GOODS_SERVICES",
      "totalAmount": "<the invoice's base amount, or null — see the notes>",
      "createdAt": "2026-07-30T10:15:00.000Z"
    }
  ],
  "totalCount": 128,
  "nextCursor": "Qx7R2mK9tB4wL0sVhN6ZpA3cJ8dF1gY5"
}
Errors
CodeStatusWhen
VALIDATION_ERROR400An unknown query parameter or an unknown enum value, periodFrom later than periodTo, or a corrupted cursor — including one whose filters or sortOrder no longer match.
UNAUTHORIZED401The request carries no valid JWT.

The set of displayStatus values is not published in this reference yet — it belongs with the invoice status reference, which this section does not cover. An empty value (?type=) means no filter at all; an unknown one is a 400, as is an unknown query parameter. Filter groups combine with AND, values inside a group with IN. Paging is keyset: the sort is always createdAt then id as a tiebreak, both in the same direction, and the cursor is an opaque token carrying a fingerprint of the filters and of sortOrder — change either and paging restarts. limit is not part of that fingerprint and may change between pages. totalCount is the size of the whole filtered set, not of what is left after the cursor. For the fixed types totalAmount is the invoice's own base amount; for an open donation it is null until money arrives, and then the sum of all receipts converted to your base currency. The JSON shape of a money value is not pinned by this version of the reference — read it from the response rather than assuming a decimal string.

Get an invoice

GET/invoices/:id

Auth: JWT or API key signature

Returns the full invoice card: header fields, the type-specific data envelope, the payment currencies frozen at creation, and the first page of payments.

Path parameters
NameTypeRequiredDefaultDescription
idstring (UUID)RequiredThe invoice id, returned as businessInvoiceId.
GET/invoices/:id
curl 'https://dev-app.tedr.com/api/invoices/6f1c1e2c-9b4c-4a2e-9f8b-0d5a1b2c3d4e' \
  -H 'Authorization: Bearer YOUR_SESSION_JWT'

Response: 200

{
  "businessInvoiceId": "6f1c1e2c-9b4c-4a2e-9f8b-0d5a1b2c3d4e",
  "type": "GOODS_SERVICES",
  "status": "ACTIVE",
  "businessInvoiceUrl": "https://pay.tedr.com/pay/PbQ7x8kR2mT4",
  "createdAt": "2026-07-30T10:15:00.000Z",
  "expiresAt": "2026-08-29T10:15:00.000Z",
  "allowedCurrencies": "<payment currencies frozen at creation>",
  "data": {
    "items": [
      {
        "title": "Consulting hour",
        "quantity": 3,
        "unitPrice": "<unit price>",
        "totalPrice": "<line total>",
        "imageUrl": null
      }
    ],
    "totalAmount": "<invoice total>",
    "orderId": "ORD-2041"
  },
  "payments": {
    "items": [],
    "totalCount": 0,
    "nextCursor": null,
    "totalBaseAmount": "<sum of the receipts, in the base currency>",
    "baseCurrency": "USD"
  }
}
Errors
CodeStatusWhen
VALIDATION_ERROR400The id is not a valid UUID.
UNAUTHORIZED401The request carries no valid JWT.
FORBIDDEN403The invoice belongs to another merchant.
BUSINESS_INVOICE_NOT_FOUND404No invoice with this id exists, or it is already soft-deleted.

The example above is abridged. It leaves out the merchant object — the merchant's first and last name — because this reference does not pin the field names inside it. Every money value on the card, including unitPrice and totalPrice on each line item, is a placeholder: the JSON shape of a money value is not pinned by this version of the reference either, so read it from the response rather than assuming a decimal string. So is allowedCurrencies, the snapshot of the currencies this invoice may be paid in. data is polymorphic and is the only part of the read path that knows about the type: for PAYME, SUBSCRIPTION and DONATION it is { amount, description, title } (amount is null for a donation, and title is only ever set there); for GOODS_SERVICES it is { items[], totalAmount, orderId }, where each item adds totalPrice and an imageUrl that is null when no image was attached. The order of the ownership checks is deliberate: an invalid id is a 400, a missing one a 404, someone else's a 403, and only then a soft-deleted one a 404 again — so a 403 does confirm that the id exists.

List invoice payments

GET/invoices/:id/payments

Auth: JWT or API key signature

Pages through the invoice’s successful receipts. One row is one actual transfer, including partial ones. The invoice card already carries the first page.

Path parameters
NameTypeRequiredDefaultDescription
idstring (UUID)RequiredThe invoice id, returned as businessInvoiceId.
Query parameters
NameTypeRequiredDefaultDescription
cursorstringOptionalPosition token taken from nextCursor. Omit it for the first page.
limitinteger, 1–100Optional20Page size.
GET/invoices/:id/payments
curl -G 'https://dev-app.tedr.com/api/invoices/6f1c1e2c-9b4c-4a2e-9f8b-0d5a1b2c3d4e/payments' \
  -H 'Authorization: Bearer YOUR_SESSION_JWT' \
  -d 'limit=20'

Response: 200

{
  "items": [
    {
      "paymentId": "9e2f7a10-3c84-4f6b-8a21-5d0e7c9b1f43",
      "status": "PAID",
      "amount": "<transfer amount, in the currency it was paid in>",
      "payerEmail": "buyer@example.com",
      "txHash": "0x3f8a9c21d4b76e05f1a8c3d92b47e6081f5a2c9d3e7b4160a8d5c2f9b1e6a473",
      "paidAt": "2026-07-31T08:20:11.000Z"
    }
  ],
  "totalCount": 3,
  "nextCursor": "Qx7R2mK9tB4wL0sVhN6ZpA3cJ8dF1gY5",
  "totalBaseAmount": "<sum of the receipts, in the base currency>",
  "baseCurrency": "USD"
}
Errors
CodeStatusWhen
VALIDATION_ERROR400The id is not a valid UUID, or the cursor is corrupted.
UNAUTHORIZED401The request carries no valid JWT.
FORBIDDEN403The invoice belongs to another merchant.
BUSINESS_INVOICE_NOT_FOUND404No invoice with this id exists, or it is already soft-deleted.

Sorting is fixed — paidAt descending with id as a tiebreak; there are no filters and no sortOrder. A row is one receipt, so attempts in PARTIALLY_PAID, PAID_LATE and BLOCKED appear here too — that list is illustrative, not exhaustive. status is the status of the parent attempt and amount is that single transaction in the currency it was paid in, which is not your base currency: baseCurrency belongs to totalBaseAmount, the sum of every row converted for you. payerEmail is null on older attempts, and totalCount covers every receipt on the invoice, not just this page. The JSON shape of a money value is not pinned by this version of the reference — read it from the response rather than assuming a decimal string.

Cancel an invoice

POST/invoices/:id/cancel

Auth: JWT or API key signature

Moves the invoice to CANCELED. Answers 200 with the same detail card as Get an invoice, not a new resource — which is why the status is 200 and not 201.

Path parameters
NameTypeRequiredDefaultDescription
idstring (UUID)RequiredThe invoice id, returned as businessInvoiceId.
POST/invoices/:id/cancel
curl -X POST 'https://dev-app.tedr.com/api/invoices/6f1c1e2c-9b4c-4a2e-9f8b-0d5a1b2c3d4e/cancel' \
  -H 'Authorization: Bearer YOUR_SESSION_JWT'

Response: 200

Errors
CodeStatusWhen
VALIDATION_ERROR400The state machine forbids the transition — cancelling from EXPIRED, for instance — or a payment has already started on a type that does not allow it.
UNAUTHORIZED401The request carries no valid JWT.
FORBIDDEN403The invoice belongs to another merchant.
BUSINESS_INVOICE_NOT_FOUND404No invoice with this id exists, or it is already soft-deleted.

Two checks run in order. The type's state machine allows the transition from ACTIVE but not from EXPIRED. On top of that, PAYME and GOODS_SERVICES refuse cancellation once a payment has started — an attempt in PROCESSING, PARTIALLY_PAID, BLOCKED, PAID, PAID_LATE or REFUNDED. A DONATION can be cancelled regardless of what it has received. Both refusals come back as VALIDATION_ERROR, not as a dedicated code. A SUBSCRIPTION plan follows its own rules: it can be cancelled from ACTIVE or PAUSED whatever its subscribers have paid, its subscribers are cancelled with it and unpaid period invoices are closed without a refund. A plan in any other state answers 409 BUSINESS_INVOICE_CANNOT_CANCEL, and a cancelled plan cannot be reactivated.

Reactivate an invoice

POST/invoices/:id/reactivate

Auth: JWT or API key signature

Takes a cancelled invoice back to ACTIVE, provided its expiresAt is still in the future or null. Answers 200 with the same detail card as Get an invoice, not a new resource — which is why the status is 200 and not 201.

Path parameters
NameTypeRequiredDefaultDescription
idstring (UUID)RequiredThe invoice id, returned as businessInvoiceId.
POST/invoices/:id/reactivate
curl -X POST 'https://dev-app.tedr.com/api/invoices/6f1c1e2c-9b4c-4a2e-9f8b-0d5a1b2c3d4e/reactivate' \
  -H 'Authorization: Bearer YOUR_SESSION_JWT'

Response: 200

Errors
CodeStatusWhen
VALIDATION_ERROR400The state machine forbids the transition, or the invoice has already expired.
UNAUTHORIZED401The request carries no valid JWT.
FORBIDDEN403The invoice belongs to another merchant.
BUSINESS_INVOICE_NOT_FOUND404No invoice with this id exists, or it is already soft-deleted.

The deadline is checked separately from the state machine: the machine is a pure function of statuses and knows nothing about time, so without that extra check an expired invoice would come back to life pointing at a payment form that is already dead. An expired invoice is refused with VALIDATION_ERROR, like any other forbidden transition.

Delete an invoice

DELETE/invoices/:id

Auth: JWT or API key signature

Soft-deletes the invoice: the row stays in the database with a deletion timestamp, but disappears from the list and from the payment form. Answers 200 with the same detail card as Get an invoice, not an empty 204.

Path parameters
NameTypeRequiredDefaultDescription
idstring (UUID)RequiredThe invoice id, returned as businessInvoiceId.
DELETE/invoices/:id
curl -X DELETE 'https://dev-app.tedr.com/api/invoices/6f1c1e2c-9b4c-4a2e-9f8b-0d5a1b2c3d4e' \
  -H 'Authorization: Bearer YOUR_SESSION_JWT'

Response: 200

Errors
CodeStatusWhen
VALIDATION_ERROR400The invoice type does not allow deletion.
BUSINESS_INVOICE_CANNOT_DELETE400The invoice already has payment activity — for a subscription plan, any payment attempt or receipt on any of its period invoices.
UNAUTHORIZED401The request carries no valid JWT.
FORBIDDEN403The invoice belongs to another merchant.
BUSINESS_INVOICE_NOT_FOUND404No invoice with this id exists, or it was already deleted — deleting twice answers 404.
SUBSCRIPTION_PLAN_NOT_CANCELED409The subscription plan has not been cancelled yet — cancel it first, then delete it.

Two guards, and they fail with different codes. A type that forbids deletion is a plain VALIDATION_ERROR; payment activity gets the dedicated BUSINESS_INVOICE_CANNOT_DELETE. The payment check uses the same attempt statuses as cancellation, but here there is no per-type bypass — the rule holds for donations too. A SUBSCRIPTION plan is stricter: it must be CANCELED first, otherwise the answer is 409 SUBSCRIPTION_PLAN_NOT_CANCELED, and none of its period invoices may carry a payment attempt or receipt — even an attempt that never received any money blocks deletion with BUSINESS_INVOICE_CANNOT_DELETE. Deleting a plan removes its subscribers and period invoices along with it. Repeat deletes are idempotent as far as the caller can tell: the second one answers 404 BUSINESS_INVOICE_NOT_FOUND.