# Fiken A resource-oriented Ruby client for the [Fiken API v2](https://api.fiken.no/api/v2/docs) — the Norwegian accounting and invoicing platform. - Personal API token **and** OAuth2 (authorization-code) authentication - Resource-oriented surface that mirrors the API (`client.invoices(slug).drafts.create(...)`) - Automatic pagination with `auto_paging_each` - HTTP errors mapped to typed exceptions, with retry on rate limits (429) and server errors ## Installation ```ruby # Gemfile gem "fiken" ``` ```sh bundle install ``` ## Authentication ### Personal API token Generate a token in Fiken (under your account settings) and pass it in: ```ruby client = Fiken::Client.new(token: ENV["FIKEN_TOKEN"]) client.user # => # client.companies.list # => # ``` ### OAuth2 (acting on behalf of other Fiken users) ```ruby oauth = Fiken::OAuth.new( client_id: ENV["FIKEN_CLIENT_ID"], client_secret: ENV["FIKEN_CLIENT_SECRET"], redirect_uri: "https://example.com/oauth/callback" ) # 1. Send the user to authorize redirect_to oauth.authorize_url(state: "csrf-token") # 2. In your callback, exchange the code for tokens token = oauth.exchange_code(params[:code]) client = Fiken::Client.new(access_token: token.access_token) # 3. Later, refresh token = oauth.refresh(token.refresh_token) ``` ## Usage Almost every resource is scoped to a company, identified by its **slug**: ```ruby slug = client.companies.list.first.slug ``` ### Reading ```ruby client.contacts(slug).list(page: 0, pageSize: 50) client.contacts(slug).find(123) client.invoices(slug).find(456) client.accounts(slug).find("1500:10001") ``` ### Pagination A list returns a `Fiken::Collection` that exposes the page metadata and can walk every page lazily: ```ruby contacts = client.contacts(slug).list contacts.result_count # total across all pages contacts.auto_paging_each do |contact| puts contact.name end ``` ### Creating and updating ```ruby # Create returns the new resource's id (parsed from the Location header) created = client.contacts(slug).create(name: "Acme AS", organizationNumber: "123456789") created.id # Contacts/drafts update via PUT; finalized invoices/projects/etc. via PATCH client.contacts(slug).update(123, email: "post@acme.no") client.invoices(slug).update(456, sentManually: true) client.contacts(slug).delete(123) ``` ### Invoices, credit notes, offers, order confirmations These share a draft → finalize lifecycle, plus counters and (where supported) sending: ```ruby invoices = client.invoices(slug) draft = invoices.drafts.create( type: "invoice", customerId: 123, lines: [{ description: "Consulting", unitPrice: 100_000, vatType: "HIGH", quantity: 1 }] ) invoice = invoices.drafts.create_document(draft.id) # finalize the draft invoices.dispatch(invoiceId: invoice.id, method: ["email"], includeDocumentAttachments: true) invoices.counter # current invoice number invoices.create_counter(value: 1000) # initialize it client.credit_notes(slug).create_full(invoiceId: invoice.id) ``` ### Sales, purchases and payments ```ruby sales = client.sales(slug) sales.create(saleNumber: "1", date: "2026-01-01", kind: "external_invoice", lines: [...]) sales.payments(5).create(date: "2026-01-05", account: "1920:10001", amount: 125_000) sales.settle(5, settledDate: "2026-01-10") sales.write_off(5) sales.delete(5, description: "duplicate") ``` ### Attachments and the inbox (file uploads) ```ruby client.invoices(slug).attachments(456).add(path: "invoice.pdf") client.invoices(slug).attachments(456).add(io: pdf_io, filename: "invoice.pdf", content_type: "application/pdf") client.inbox(slug).create(path: "receipt.pdf", name: "Office supplies") ``` ## Error handling Non-success responses raise a typed subclass of `Fiken::Error`, each carrying `#status` and `#body`: ```ruby begin client.invoices(slug).find(999_999) rescue Fiken::NotFound => e e.status # => 404 rescue Fiken::RateLimited # retried automatically first; raised only if retries are exhausted rescue Fiken::Error => e warn e.message end ``` `BadRequest` (400), `Unauthorized` (401), `Forbidden` (403), `NotFound` (404), `UnprocessableEntity` (422), `RateLimited` (429) and `ServerError` (5xx) are all provided. ## Available resources `user`, `companies`, `accounts`, `account_balances`, `bank_accounts`, `bank_balances`, `contacts` (+ `contact_persons`), `groups`, `products`, `journal_entries`, `transactions`, `invoices`, `credit_notes`, `offers`, `order_confirmations`, `sales`, `purchases`, `inbox`, `projects`, `activities`, `time_entries`, `time_users`. ## Configuration ```ruby Fiken::Client.new( token: ENV["FIKEN_TOKEN"], base_url: "https://api.fiken.no/api/v2", # default user_agent: "my-app/1.0", retry_options: { max: 3 }, # passed to faraday-retry logger: Logger.new($stdout) ) ``` ## Development ```sh bin/setup # or: bundle install bundle exec rspec bundle exec rubocop ``` ## License The gem is available as open source under the terms of the [MIT License](LICENSE.txt).