A Ruby Gem to interact with the MOCO API. This gem provides a modern, Ruby-esque interface (MOCO::Client) for interacting with the MOCO API.
Install the gem and add to the application's Gemfile by executing:
$ bundle add moco-ruby
If bundler is not being used to manage dependencies, install the gem by executing:
$ gem install moco-ruby
require 'moco'
moco = MOCO::Client.new(
subdomain: "your-subdomain", # Your MOCO subdomain
api_key: "your-api-key" # Your MOCO API key
)Collections are accessed dynamically using pluralized entity names (following Rails conventions):
projects = moco.projects
activities = moco.activities
users = moco.users
# ... and so on for all supported entities# Get all entities in a collection
all_projects = moco.projects.all
# Get entities matching specific criteria
active_projects = moco.projects.where(active: true)
recent_activities = moco.activities.where(date: ">=2024-01-01")
# Get a specific entity by ID
project = moco.projects.find(12345)
user = moco.users.find(678)# Create a new project
new_project = moco.projects.create(
name: "New Website Project",
customer_id: 987,
billable: true
)
puts "Created project: #{new_project.name} (ID: #{new_project.id})"
# Create a new time entry (activity)
new_activity = moco.activities.create(
date: "2024-04-10",
project_id: new_project.id,
task_id: new_project.tasks.first.id, # Assumes project has tasks
hours: 3.5,
description: "Implemented feature X"
)
puts "Created activity: #{new_activity.description} on #{new_activity.date}"Modify attributes and call save:
project = moco.projects.find(12345)
project.name = "Updated Project Name"
project.billable = false
if project.save
puts "Project updated successfully."
else
puts "Failed to update project: #{project.errors.full_messages.join(", ")}" # Assuming error handling exists
end
# You can also update directly via the collection
moco.projects.update(12345, name: "Another Update", active: false)# Delete by object
activity = moco.activities.find(9876)
if activity&.delete
puts "Activity deleted."
end
# Delete by ID via collection
if moco.activities.delete(9876)
puts "Activity deleted."
endEntities provide methods to access related entities easily:
project = moco.projects.find(12345)
# Get tasks associated with the project
tasks = project.tasks # Returns a collection proxy for tasks
puts "Tasks for project '#{project.name}': #{tasks.map(&:name).join(', ')}"
# Get activities for the project
project_activities = project.activities
puts "Activities count: #{project_activities.size}"
# Get the customer (company) for the project
customer = project.customer # Returns a MOCO::Company object
puts "Customer: #{customer.name}"
# ---
activity = moco.activities.find(9876)
# Get the associated project, task, and user
act_project = activity.project
act_task = activity.task
act_user = activity.user
puts "Activity by #{act_user.firstname} on project '#{act_project.name}' (Task: #{act_task.name})"The gem supports ActiveRecord-style operations on nested resources:
project = moco.projects.find(12345)
# Create a new task for the project
new_task = project.tasks.create(
name: "New Feature Development",
billable: true,
active: true,
budget: 40,
hourly_rate: 120
)
puts "Created task: #{new_task.name} (ID: #{new_task.id})"
# Delete all tasks for a project
project.tasks.destroy_all
# Query tasks with conditions
billable_tasks = project.tasks.where(billable: true).all
puts "Billable tasks: #{billable_tasks.map(&:name).join(', ')}"
# Find a specific task
dev_task = project.tasks.find_by(name: "Development")Access the current user's profile:
profile = moco.profile
puts "Logged in as: #{profile.firstname} #{profile.lastname}"Exchange email/password for an API key, or verify an existing key:
# Exchange credentials for an API key (no Client needed)
session = MOCO::Session.create(
subdomain: "your-subdomain",
email: "you@example.com",
password: "secret"
)
api_key = session["api_key"]
# Verify the configured API key for an existing client
identity = moco.session.verify
puts "Authenticated as user #{identity['id']} (#{identity['uuid']})"Attachments are nested under the parent document and use base64-encoded uploads:
require "base64"
invoice = moco.invoices.find(123)
invoice.attachments.all
invoice.attachments.create(
attachment: {
filename: "appendix.pdf",
base64: Base64.strict_encode64(File.read("appendix.pdf"))
}
)
invoice.attachments.find(42).destroy
# Offer attachments work the same way:
moco.offers.find(123).attachments.create(
attachment: { filename: "quote-details.pdf", base64: ... }
)The OpenAPI reference defines supported
operations, required fields, permissions, and filters. Collections accept API fields
unchanged through create, update, and where; response fields are available as
entity attributes. Only call operations documented for that resource (for example,
todos is read-only, and invoices do not have a general update endpoint).
# Offer confirmations: list, find, create, update, delete, and PDF download
confirmation = moco.offer_confirmations.create(offer_id: 123, title: "Order confirmation")
confirmation.update(footer_raw: "<p>Thank you!</p>")
confirmation.pdf(letter_paper_id: 42)
moco.offers.find(123).confirmations.all
# Offers support full updates and deletion
moco.offers.update(123, title: "Website proposal", items: [...])
moco.offers.delete(123)
# Revenue categories: list, find, create, update, and delete
moco.revenue_categories.where(term: "Consulting").all
moco.revenue_categories.create(name: "Consulting", assignment_kind: "tasks_and_expenses")
# Purchase drafts: list, find, create, delete, and PDF download
moco.purchase_drafts.create(
title: "Supplier invoice",
file: { filename: "invoice.pdf", base64: Base64.strict_encode64(File.binread("invoice.pdf")) }
)
# Company archiving and early execution of recurring additional services
moco.companies.find(123).archive
moco.companies.find(123).unarchive
moco.projects.find(123).recurring_expenses.find(456).recur
# Purchase budgets support creation, updates, deletion, and annual targets
moco.purchase_budgets.where(year: 2026).all
budget = moco.purchase_budgets.create(title: "Infrastructure")
budget.update_target(target_amount: 12_000, year: 2026)
# Planned purchases support list, find, create, update, and delete
moco.planned_purchases.create(
date: "2026-08-01", title: "Hosting", net_total: 100, currency: "EUR", user_id: 123
)
# Invoice/purchase approvals: list, find, create, delete, approve, and deny
approval = moco.approvals.create(target_type: "purchase", target_id: 123, approver_id: 456)
approval.approve
# Or: approval.deny(note: "Please check the amount")
# Absence requests: list, find, create, and delete
moco.absence_requests.where(user_id: 123).all
moco.absence_requests.find(456).destroy(comment: "Cancelled")
# To-dos are read-only
moco.todos.where(completed: false).all
moco.todos.find(123)
# Catalog items are listed in the parent; individual items support CRUD
service = moco.catalog_services.find(123)
service.items.all
service.items.create(type: "item", title: "Consulting", quantity: 1, unit: "h", unit_price: 150)
service.items.find(456).update(unit_cost: 75)
# Online customer approval for offers
moco.offers.find(123).activate_customer_approval
moco.offers.find(123).customer_approval
moco.offers.find(123).deactivate_customer_approvalNew fields need no schema configuration. Examples include skip_favorite on projects,
task_id on planning entries, invoice_format, default_payment_means,
customer_vat_code_id, supplier_vat_code_id, bank_owner, and bank_bic on companies,
welcome_email on users, and created_at on comments. User creation does not require
password. Filter users by email and schedules by absence_request_id.
Use vat_code_id on invoices and purchase items rather than the deprecated tax
value. Offer/invoice items accept revenue_category_id; offer items also accept
unit_cost. Project expenses accept revenue_category_id, service_period_from,
service_period_to, planned_purchase_date, and cost_total_planned.
Response fields such as activity worked_seconds, reminder status / file_url,
invoice creditor_reference, comment impersonating_user, and draft net_total /
gross_total are exposed automatically.
Bearer authentication is optional; Token authentication is the default:
moco = MOCO::Client.new(subdomain: "your-subdomain", api_key: "your-api-key", auth_scheme: :bearer)Access read-only report endpoints:
# Absences report
absences = moco.reports.absences(year: 2024)
# Utilization report (requires date range)
utilization = moco.reports.utilization(from: "2024-01-01", to: "2024-12-31")
# Financial reports
cashflow = moco.reports.cashflow(from: "2024-01-01", to: "2024-03-31")
finance = moco.reports.finance(from: "2024-01-01", to: "2024-03-31")
# Planned versus tracked hours (optional date range and filters)
planned_vs_tracked = moco.reports.planned_vs_tracked(
from: "2026-01-01", to: "2026-01-31", user_ids: "123,456", project_id: 789
)The gem supports all MOCO API entities with a Ruby-esque interface:
Core:
Project, Activity, User, Company, Task, Invoice, Deal, Expense, WebHook, Schedule, Presence, Holiday, PlanningEntry
Business:
Contact, Offer, OfferConfirmation, Purchase, PlannedPurchase, Receipt, Comment, Tag, Tagging, DealCategory, ProjectGroup, Unit, Approval, AbsenceRequest, Todo (read-only)
Account Settings:
CatalogService, CustomProperty, ExpenseTemplate, FixedCost, HourlyRate, InternalHourlyRate, TaskTemplate, UserRole
Financial:
VatCodeSale, VatCodePurchase, RevenueCategory, PurchaseCategory, PurchaseDraft, PurchaseBudget, PurchasePayment
Bookkeeping:
InvoiceBookkeepingExport, PurchaseBookkeepingExport
Nested Resources:
Employment, WorkTimeAdjustment, ProjectContract, PaymentSchedule, RecurringExpense, InvoicePayment, InvoiceReminder, OfferApproval, InvoiceAttachment, OfferAttachment, CatalogServiceItem
Misc:
LetterPaper (read-only), Session (for API key exchange/verification)
Access them via the client using their plural, snake_case names (e.g., moco.planning_entries, moco.vat_code_sales, moco.letter_papers). Attachments are accessed via the parent: invoice.attachments, offer.attachments.
These command-line utilities provide helpful shortcuts. They can use credentials and configuration from a config.yml file (see config.yml.sample) in the current directory or accept parameters.
A wrapper around curl to easily make authenticated requests to your MOCO instance API. Useful for testing or exploring endpoints.
Usage: mocurl.rb [options] url
mocurl.rb [options] subdomain path
-X, --method METHOD Set HTTP method to use (GET, POST, PUT, DELETE)
-d, --data DATA Data to send to server (JSON format) for POST/PUT
-a, --api-key API_KEY Manually specify MOCO API key (overrides config.yml)
-n, --no-format Disable JSON pretty-printing of the response
-v, --verbose Show additional request and response information
-h, --help Show this message
Example: mocurl.rb your-subdomain projects/12345
Syncs activity data (time entries) between two MOCO instances (source and target). It uses fuzzy matching to map projects and tasks between the instances.
Important:
- Always use
--dry-runfirst to verify the matching and intended actions. - Use date filters (
--from,--to) to limit the scope.
Usage: sync_activity.rb [options] source_subdomain target_subdomain
-f, --from DATE Start date for sync (YYYY-MM-DD)
-t, --to DATE End date for sync (YYYY-MM-DD)
-p, --project PROJECT_ID Source Project ID to filter by (optional)
-c, --company COMPANY_ID Source Company ID to filter by (optional)
-g, --term TERM Term to filter source activities by (optional)
-n, --dry-run Perform matching and show planned actions, but do not modify target instance
--match-project-threshold VALUE
Fuzzy match threshold for projects (0.0 - 1.0), default 0.8
--match-task-threshold VALUE Fuzzy match threshold for tasks (0.0 - 1.0), default 0.45
--default-task TASK_NAME Map unmatched tasks to this default task instead of creating new tasks
-d, --debug Enable debug output
-h, --help Show this message
Example: sync_activity.rb --from 2024-04-01 --to 2024-04-10 --dry-run source-instance target-instance
Using Default Task Mapping: If your target account has limited permissions and cannot create tasks, or if you want to consolidate multiple source tasks into a single target task, use the --default-task flag:
sync_activity.rb --from 2024-04-01 --to 2024-04-10 --default-task "Other" source-instance target-instanceThis will map any unmatched source tasks to a task named "Other" in the corresponding target project, avoiding the need to create new tasks.
After checking out the repo, run bin/setup to install dependencies.
The gem includes unit tests (mocked) and integration tests (live API):
# Unit tests (mocked, fast)
bundle exec rake test
bundle exec ruby -Ilib -Itest test/test_api_updates.rb
bundle exec ruby -Ilib -Itest test/test_v2_api.rb
bundle exec ruby -Ilib -Itest test/test_new_entities.rb
# Integration tests (requires .env credentials)
bundle exec ruby -Ilib -Itest test/test_integration.rb
bundle exec ruby -Ilib -Itest test/test_comprehensive.rbFor integration tests, create a .env file with your test instance credentials:
MOCO_API_TEST_SUBDOMAIN=your-test-subdomain
MOCO_API_TEST_API_KEY=your-test-api-key
Note: The MOCO API has rate limits. The gem automatically retries rate-limited requests with exponential backoff.
To install this gem onto your local machine, run bundle exec rake install.
To release a new version, update the version number in version.rb, update the CHANGELOG.md, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.
Bug reports and pull requests are welcome on GitHub at https://github.com/starsong-consulting/moco-ruby.
The gem is available as open source under the terms of the Apache License, Version 2.0.