Bidrik API reference

Bidrik API

Bidrik is a quoting and proposal platform for sales teams: create quotes from templates, send them to customers and get them accepted and e-signed online. The API gives other applications access to a Bidrik account's quotes, customers and templates. It is the API behind the Bidrik integration on Zapier.

Base URL: https://api.bidrik.com. Every request uses HTTPS and JSON. Questions: info@bidrik.com.

Authentication

The API uses OAuth 2.0 with the authorization code grant (RFC 6749). PKCE with S256 is supported (RFC 7636). Client credentials are issued by Bidrik; contact us to register an application.

Only account administrators can connect an application. The connection acts on behalf of the administrator's Bidrik account and sees all of its quotes and customers.

StepEndpointNotes
1. AuthorizeGET /oauth/authorizeQuery: response_type=code, client_id, redirect_uri (must match a registered URI exactly), state, optional code_challenge + code_challenge_method=S256. The user signs in to Bidrik if needed and approves. Bidrik redirects to redirect_uri with code and state, or with error=access_denied.
2. Exchange the codePOST /oauth/tokenForm or JSON body: grant_type=authorization_code, code, redirect_uri, client_id, client_secret (or HTTP Basic), code_verifier if PKCE was used. Codes expire after 10 minutes and work once.
3. Call the APIAuthorization: Bearer <access_token>Access tokens last 60 minutes.
4. RefreshPOST /oauth/tokengrant_type=refresh_token, refresh_token, client credentials. Returns a new access token and a new refresh token; the old refresh token stops working. A refresh token unused for 90 days expires. Reusing an old refresh token revokes the connection.
RevokePOST /oauth/revoketoken (access or refresh) and client credentials (RFC 7009). Revokes the whole connection. Always answers 200.

Token response:

{
  "access_token": "bat_…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "brt_…"
}

A connection stops working when the user is removed or deactivated, loses the administrator role, or the Bidrik account is closed. API calls then answer 401 and refreshing fails with invalid_grant; the user needs to reconnect.

Conventions

Errors

Errors answer a non-2xx status and this body:

{ "error": { "code": "duplicate_customer", "message": "A customer with this name already exists. …" } }
StatusCodeMeaning
400invalid_parameterA parameter is missing or malformed; error.field names it.
400invalid_jsonThe body is not a JSON object.
401missing_token, invalid_token, user_inactive, account_inactive, admin_required, not_account_memberThe access token cannot be used. Refresh it; if refreshing fails, reconnect.
402trial_limit_reachedThe account is on a trial and has used its documents. Carries used and limit.
404not_found, template_not_found, customer_not_foundNo such object in the connected account.
405method_not_allowedWrong HTTP method; see Allow.
409duplicate_customerAnother customer has the same name. Send allow_duplicate: true to save anyway.
409fortnox_sync_activeThe account syncs customers with Fortnox; customers cannot be written through the API.
422validation_failedBidrik rejected the object; error.fields maps field to reason.
429rate_limitedToo many requests.
500internal_errorOur fault. error.reference helps us find it.

Account

GET /v1/me

The connected user and account. Used to test a connection.

{
  "id": "66e0c2f1a9b4c3d2e1f00c33",
  "email": "anna@example.com",
  "name": "Anna Andersson",
  "account": { "id": "66e0c2f1a9b4c3d2e1f00d44", "name": "Example Sales AB" },
  "plan": "paid",
  "documents": { "used": 212, "limit": null }
}

plan is paid or trial. On a trial, documents.limit is the number of documents (quotes, templates, product sheets and agreements) the account may have.

Quotes

GET /v1/quotes

Quotes in the account, newest first.

QueryDescription
sortcreated (default) or updated. Event orders: sent, opened, accepted, rejected — most recent event first, and only quotes where that event has happened, whatever their status is now. expires orders by expiry date, latest first.
statusOnly quotes with this current status: draft, sent, opened, accepted, rejected, delivered, followup, expired.
expiredtrue: only sent quotes whose expiry date is before today (UTC) and that are not accepted, rejected or delivered. A quote valid until a date stays valid all of that day. Cannot be combined with status. false: only quotes that have not expired.
customer_idOnly quotes for this customer.
limit, pagePaging.

Response: { "data": [ quote, … ] }. A quote:

{
  "id": "66e0c2f1a9b4c3d2e1f00a11",
  "number": "1042",
  "title": "Office renovation",
  "status": "accepted",
  "value": 125000,
  "currency": "SEK",
  "customer_id": "66e0c2f1a9b4c3d2e1f00b22",
  "customer_name": "Example AB",
  "owner_name": "Anna Andersson",
  "owner_email": "anna@example.com",
  "created_at": "2026-09-01T08:12:00.000Z",
  "updated_at": "2026-09-03T14:40:12.000Z",
  "valid_from": "2026-09-01T00:00:00.000Z",
  "expires_at": "2026-10-01T00:00:00.000Z",
  "sent_at": "2026-09-01T09:00:00.000Z",
  "opened_at": "2026-09-02T07:31:00.000Z",
  "accepted_at": "2026-09-03T14:40:12.000Z",
  "rejected_at": null
}
FieldDescription
numberThe quote number shown to the customer.
statusaccepted means the customer accepted or signed the quote.
valueTotal value as a number, in currency.
owner_*The Bidrik user responsible for the quote.

GET /v1/quotes/{id}

One quote. 404 not_found if it does not exist in the account.

POST /v1/quotes

Creates a draft quote from a template, for a customer. The quote gets the next quote number and the connected user as owner, exactly as if it was created in Bidrik. It is not sent.

Body fieldDescription
template_idrequiredTemplate to create the quote from. See GET /v1/templates.
customer_idrequiredCustomer the quote is for.
titleoptionalTitle of the quote. Defaults to the template's name.
POST /v1/quotes
{ "template_id": "66e0c2f1a9b4c3d2e1f00e55", "customer_id": "66e0c2f1a9b4c3d2e1f00b22", "title": "Office renovation" }

Answers 201 with the quote. 402 trial_limit_reached when a trial account has used its documents.

Customers

GET /v1/customers

Customers in the account, most recently created first. Response: { "data": [ customer, … ] }. A customer:

{
  "id": "66e0c2f1a9b4c3d2e1f00b22",
  "name": "Example AB",
  "is_private": false,
  "org_number": "556677-8899",
  "customer_number": "1001",
  "email": "hello@example.com",
  "website": "https://example.com",
  "vat_number": "SE556677889901",
  "currency": "SEK",
  "contact": {
    "first_name": "Erik",
    "last_name": "Svensson",
    "email": "erik@example.com",
    "phone": "+46 70 123 45 67",
    "title": "CEO"
  },
  "address": {
    "street": "Storgatan 1",
    "postal_code": "111 22",
    "city": "Stockholm",
    "country": "Sweden"
  },
  "owner_name": "Anna Andersson",
  "owner_email": "anna@example.com",
  "created_at": "2026-08-20T10:00:00.000Z"
}

A private customer (is_private: true) is a person; its name is the contact's name.

GET /v1/customers/{id}

One customer. 404 not_found if it does not exist in the account.

POST /v1/customers

Creates a customer. Its owner is the connected user unless owner_email names another user. Answers 201 with the customer.

Body fieldDescription
nameCompany name. Required unless is_private.
is_privatetrue for a private person. Then contact.first_name is required.
org_number, customer_number, email, website, vat_numberText.
currencyThree-letter code, e.g. SEK.
contactObject: first_name, last_name, email, phone, title.
addressObject: street, postal_code, city, country.
owner_emailEmail of the Bidrik user responsible for the customer. Must be an active user in the account, otherwise 400 invalid_parameter and nothing is saved.
allow_duplicatetrue to save even if another customer has the same name. Otherwise 409 duplicate_customer.
POST /v1/customers
{
  "name": "Example AB",
  "org_number": "556677-8899",
  "email": "hello@example.com",
  "contact": { "first_name": "Erik", "last_name": "Svensson", "email": "erik@example.com" },
  "address": { "city": "Stockholm", "country": "Sweden" }
}

Accounts that sync customers with Fortnox answer 409 fortnox_sync_active.

PATCH /v1/customers/{id}

Updates the fields you send; everything else is kept. Same fields as POST /v1/customers. An empty string leaves a field unchanged; send null to clear it. Answers 200 with the customer.

PATCH /v1/customers/66e0c2f1a9b4c3d2e1f00b22
{ "email": "billing@example.com", "contact": { "phone": "+46 70 765 43 21" } }

Templates

GET /v1/templates

The account's quote templates, by name. Used to pick a template for POST /v1/quotes.

{ "data": [ { "id": "66e0c2f1a9b4c3d2e1f00e55", "name": "Standard quote", "updated_at": "2026-06-11T12:00:00.000Z" } ] }

Version 1. Breaking changes get a new version prefix; additions do not.