Add README with auth, usage, pagination, uploads, and error-handling examples
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
# 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 # => #<Fiken::Object name="..." email="...">
|
||||
client.companies.list # => #<Fiken::Collection ...>
|
||||
```
|
||||
|
||||
### 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).
|
||||
Reference in New Issue
Block a user