gnubok

Suppliers

AP-side counterparties. Mirrors customers on the supplier vertical.

Endpoints


GET /api/v1/companies/:companyId/suppliers {#get-suppliers-list}

suppliers.list · scope suppliers:read

List suppliers for a company.

Returns active suppliers in created-first order. Pass ?include_archived=true to include archived rows. Use ?search to match against name or org_number.

Use when: You need a supplier roster — for building a UI picker, resolving a supplier_id before registering a supplier invoice, or syncing an external AP system.

Don't use for: Fetching a single supplier you already know the id of — use GET /api/v1/companies/{companyId}/suppliers/{id}. Customers are a separate resource.

Pitfalls

  • Archived suppliers are hidden by default; the dashboard makes the same choice.
  • org_number identifies legal entities only — suppliers currently have no individual type, so the field is Bolagsverket public-record data when present.
  • vat_number is stored as supplied; unlike customers, suppliers are not auto-validated against VIES on create. Validate externally if the integration requires it.

Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: no

Example response

{
  "data": [
    {
      "id": "a8f1…",
      "name": "Office Depot AB",
      "supplier_type": "swedish_business",
      "email": "invoices@officedepot.example",
      "org_number": "556677-8899",
      "vat_number": "SE556677889901",
      "default_payment_terms": 30,
      "default_currency": "SEK",
      "archived_at": null,
      "created_at": "2026-04-12T08:30:00Z"
    }
  ],
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12",
    "next_cursor": null
  }
}

GET /api/v1/companies/:companyId/suppliers/:id {#get-suppliers-get}

suppliers.get · scope suppliers:read

Retrieve a single supplier by id.

Returns the full supplier record. Pass ?expand=supplier_invoices to embed any open supplier invoices (registered / approved / partially_paid / overdue / disputed) for the supplier in the same response.

Use when: You need the full supplier record — address, payment terms, banking details, default expense account — before booking a supplier invoice or syncing to an external AP system.

Don't use for: Listing suppliers (use the list endpoint). Looking up customer or employee records (different resources).

Pitfalls

  • archived_at is non-null when the supplier has been soft-deleted; the supplier is still queryable by id but excluded from default lists.
  • Banking fields (bankgiro / plusgiro / iban / bic) are stored as supplied; no Luhn or IBAN check is performed at this layer.

Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: no

Example response

{
  "data": {
    "id": "a8f1…",
    "name": "Office Depot AB",
    "supplier_type": "swedish_business",
    "email": "invoices@officedepot.example",
    "org_number": "556677-8899",
    "bankgiro": "123-4567",
    "default_expense_account": "5410",
    "default_payment_terms": 30,
    "default_currency": "SEK",
    "archived_at": null,
    "created_at": "2026-04-12T08:30:00Z",
    "updated_at": "2026-04-30T11:22:09Z"
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

POST /api/v1/companies/:companyId/suppliers {#post-suppliers-create}

suppliers.create · scope suppliers:write

Create a supplier.

Creates a new supplier for the company. Requires Idempotency-Key (UUID). Supports ?dry_run=true for input validation without committing — the dry-run response shows the would-be record minus id and timestamps.

Use when: You need to register a new supplier before booking supplier invoices against them. Use dry-run first to catch validation errors before committing.

Don't use for: Updating an existing supplier (PATCH instead). Creating customers (different resource).

Pitfalls

  • Idempotency-Key is mandatory — calls without it return 400 VALIDATION_ERROR.
  • org_number uniqueness is enforced at the database level; duplicate inserts return 409 SUPPLIER_DUPLICATE_ORG_NUMBER.
  • Unlike customers, suppliers carry no vat_number_validated flag — vat_number is stored as supplied without VIES verification. Validate externally if your workflow requires it.
  • default_expense_account is a BAS account number (e.g. "5410"); the value is stored as-is and used as the suggested debit account when supplier invoices are booked.

Risk: low · Idempotent: yes · Reversible: yes · Dry-run supported: yes

Example request

{
  "name": "Office Depot AB",
  "supplier_type": "swedish_business",
  "email": "invoices@officedepot.example",
  "org_number": "556677-8899",
  "bankgiro": "123-4567",
  "default_expense_account": "5410",
  "default_payment_terms": 30,
  "default_currency": "SEK"
}

Example response

{
  "data": {
    "id": "0e9c…",
    "name": "Office Depot AB",
    "supplier_type": "swedish_business",
    "email": "invoices@officedepot.example",
    "org_number": "556677-8899",
    "bankgiro": "123-4567",
    "default_expense_account": "5410",
    "default_payment_terms": 30,
    "default_currency": "SEK",
    "archived_at": null,
    "created_at": "2026-05-13T15:00:00Z",
    "updated_at": "2026-05-13T15:00:00Z"
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

POST /api/v1/companies/:companyId/suppliers/bulk-create {#post-suppliers-bulk-create}

suppliers.bulk-create · scope suppliers:write

Create up to 50 suppliers in one call (partial-success).

Bulk-create endpoint mirroring /customers/bulk-create. Each supplier is validated and inserted independently — per-item failures do not roll back items that succeeded. Returns a results array plus a summary. Idempotent over the whole batch. Dry-runnable.

Use when: You're importing a roster of suppliers from another AP system, or seeding a fresh company with its existing vendor list. Use dry-run first to validate the batch.

Don't use for: Updating existing suppliers — PATCH /suppliers/{id} once per supplier. Bulk uploads of > 50 suppliers — split into pages of 50. Transactional all-or-nothing imports — passing all_or_nothing: true returns 501 NOT_IMPLEMENTED.

Pitfalls

  • Idempotency-Key is mandatory and covers the WHOLE batch. A retried bulk-create returns the cached full response — it does not retry only the failed items.
  • Passing all_or_nothing: true returns 501 NOT_IMPLEMENTED. Today only partial-success batches exist; omit the flag or pass false.
  • org_number uniqueness is enforced at the DB level — items with duplicates fail individually with SUPPLIER_DUPLICATE_ORG_NUMBER.
  • No VIES validation runs per item; vat_number is stored as supplied. Validate externally if your workflow requires it.

Risk: low · Idempotent: yes · Reversible: yes · Dry-run supported: yes

Example request

{
  "suppliers": [
    {
      "name": "Office Depot AB",
      "supplier_type": "swedish_business",
      "org_number": "556677-8899"
    },
    {
      "name": "Cloud Hosting GmbH",
      "supplier_type": "eu_business",
      "vat_number": "DE123456789"
    }
  ]
}

Example response

{
  "data": {
    "results": [
      {
        "ok": true,
        "request_index": 0,
        "data": {
          "id": "0e9c…",
          "name": "Office Depot AB"
        }
      },
      {
        "ok": true,
        "request_index": 1,
        "data": {
          "id": "4d2a…",
          "name": "Cloud Hosting GmbH"
        }
      }
    ],
    "summary": {
      "total": 2,
      "succeeded": 2,
      "failed": 0
    }
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

PATCH /api/v1/companies/:companyId/suppliers/:id {#patch-suppliers-update}

suppliers.update · scope suppliers:write

Partially update a supplier.

Patches the supplier with the supplied fields. All fields optional. Idempotent (mandatory Idempotency-Key). Dry-runnable.

Use when: You need to change a supplier's contact details, payment terms, banking info, default expense account, or VAT number. Use dry-run first to confirm the merged record before committing.

Don't use for: Archiving a supplier (use DELETE — sets archived_at). Replacing the entire record (no PUT verb is exposed; PATCH is partial).

Pitfalls

  • Idempotency-Key is mandatory; calls without it return 400.
  • org_number uniqueness is enforced at DB level — 23505 → 409 SUPPLIER_DUPLICATE_ORG_NUMBER.
  • Changing default_expense_account does not retroactively rebook prior supplier invoices — only future bookings pick up the new default.

Risk: low · Idempotent: yes · Reversible: yes · Dry-run supported: yes

Example request

{
  "default_payment_terms": 14,
  "notes": "New payment terms agreed 2026-05-12."
}

Example response

{
  "data": {
    "id": "0e9c…",
    "name": "Office Depot AB",
    "default_payment_terms": 14,
    "notes": "New payment terms agreed 2026-05-12."
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

DELETE /api/v1/companies/:companyId/suppliers/:id {#delete-suppliers-delete}

suppliers.delete · scope suppliers:write

Archive a supplier (soft-delete).

Sets archived_at on the supplier; the record is preserved (supplier invoices and audit history remain intact) but excluded from default list responses. To un-archive, PATCH archived_at back to null. Idempotent — archiving an already-archived supplier is a no-op. Dry-runnable.

Use when: You want to remove a supplier from active rosters without losing their history. Idempotent: re-archiving is safe.

Don't use for: Permanently deleting a supplier with all history — the public API does not expose hard-delete. GDPR erasure requests go through a dedicated workflow.

Pitfalls

  • Idempotency-Key is mandatory.
  • A supplier with any open supplier invoice (registered / approved / partially_paid / overdue / disputed) cannot be archived — returns 409 SUPPLIER_HAS_INVOICES. Close the invoices first. This protects BFL 7 kap audit: the supplier record is the canonical source of seller name/address for invoice reissuance.
  • 204 No Content is returned on success — there is no response body to parse.

Risk: medium · Idempotent: yes · Reversible: yes · Dry-run supported: yes

Example response

{
  "data": null,
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}