{"id":32287324,"url":"https://github.com/dartfoundry/outseta","last_synced_at":"2026-02-21T07:03:11.470Z","repository":{"id":295338676,"uuid":"988282534","full_name":"dartfoundry/outseta","owner":"dartfoundry","description":"Dart REST API client for projects using the Outseta membership operating system.","archived":false,"fork":false,"pushed_at":"2025-05-26T08:26:32.000Z","size":1655,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-23T02:11:30.302Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dartfoundry.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-05-22T10:16:38.000Z","updated_at":"2025-05-26T08:26:35.000Z","dependencies_parsed_at":"2025-05-25T01:29:45.511Z","dependency_job_id":"eab378c6-a00e-4d9e-9dfa-8efd4a99c6c4","html_url":"https://github.com/dartfoundry/outseta","commit_stats":null,"previous_names":["dartfoundry/outseta"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dartfoundry/outseta","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dartfoundry%2Foutseta","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dartfoundry%2Foutseta/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dartfoundry%2Foutseta/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dartfoundry%2Foutseta/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dartfoundry","download_url":"https://codeload.github.com/dartfoundry/outseta/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dartfoundry%2Foutseta/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29675925,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-21T06:23:40.028Z","status":"ssl_error","status_checked_at":"2026-02-21T06:23:39.222Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-10-23T02:04:07.437Z","updated_at":"2026-02-21T07:03:11.464Z","avatar_url":"https://github.com/dartfoundry.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Outseta Dart API Client\n\n[![Screenshots](media/header.png)](https://github.com/dartfoundry/outseta)\n\nA comprehensive Dart API client for the [Outseta.com](https://www.outseta.com?via=dartfoundry) REST API V1. This library provides a type-safe interface to interact with all Outseta API endpoints including CRM, Billing, Marketing, and Support functions.\n\n## 🚨 Breaking Changes in v2.0.0\n\nThe `User` model has been renamed to `Profile` throughout the codebase. If you're upgrading from v1.x, you'll need to:\n- Update import statements from `User` to `Profile`\n- Change variable declarations from `User` to `Profile`\n- Update method calls that returned `User` objects to expect `Profile` objects\n\n[![pub package](https://img.shields.io/pub/v/outseta.svg)](https://pub.dev/packages/outseta)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)\n\nThe change reflects the official Outseta API more accurately. The refactoring maintains 100% functional compatibility - only the naming has changed. All existing functionality, JSON serialization/deserialization, API endpoints, and behavior remain exactly the same.\n\n## Features\n\n- **Authentication**: Support for both API key auth (server-side) and bearer token auth (client-side)\n- **CRM**: Manage people, accounts, and deals\n- **Billing**: Handle subscriptions, invoices, plans, and payments\n- **Marketing**: Work with email campaigns, lists, and subscribers\n- **Support**: Create and manage support tickets\n- **User Profile**: Manage user profiles and authentication\n- **Type-safe**: All models are fully typed with JSON serialization support\n- **Pagination**: Built-in support for paginated responses\n\n[![Screenshots](media/try-outseta.png)](https://outseta.com?via=dartfoundry)\n\n## API Overview\n\nYou can read the accompanying article at [DartFoundry.com](https://dartfoundry.com/introducing-the-outseta-dart-api-client-powering-membership-businesses-with-dart).\n\n| Module | Functionality | Key Methods | Models |\n|--------|--------------|-------------|--------|\n| **CRM** | Customer relationship management | `getPeople()`, `createAccount()`, `getDeal()` | `Person`, `Account`, `Deal` |\n| **Billing** | Subscription and payment management | `getPlans()`, `createSubscription()`, `getInvoice()` | `Plan`, `Subscription`, `Invoice`, `Payment` |\n| **Marketing** | Email campaigns and lists | `getLists()`, `createEmail()`, `addSubscriber()` | `EmailList`, `Email` |\n| **Support** | Help desk and tickets | `getTickets()`, `addComment()`, `changeStatus()` | `Ticket` |\n| **User Profile** | User authentication and profile | `getCurrentUser()`, `updateProfilePicture()`, `changePassword()` | `Profile` |\n\n## Getting Started\n\nAdd the package to your `pubspec.yaml`:\n\n```yaml\ndependencies:\n  outseta: ^2.0.0\n```\n\nThen run:\n\n```bash\ndart pub get\n```\n\n## Usage\n\n### Initializing the client\n\n```dart\nimport 'package:outseta/outseta.dart';\n\n// For server-side usage with API keys\nfinal client = OutsetaClient(\n  baseUrl: 'https://your-domain.outseta.com/api/v1',\n  auth: ApiKeyAuth(\n    apiKey: 'your-api-key',\n    secretKey: 'your-secret-key',\n  ),\n);\n\n// For client-side usage with bearer token\nfinal client = OutsetaClient(\n  baseUrl: 'https://your-domain.outseta.com/api/v1',\n  auth: BearerTokenAuth(\n    accessToken: 'user-access-token',\n  ),\n);\n```\n\n### Working with the CRM\n\n```dart\n// Get people\nfinal peopleResponse = await client.crm.getPeople(limit: 10);\nfor (final person in peopleResponse.items) {\n  print('${person.fullName} (${person.email})');\n}\n\n// Get a specific account\nfinal account = await client.crm.getAccount('account-uid');\nprint(account.name);\n\n// Create a new person\nfinal newPerson = Person(\n  firstName: 'John',\n  lastName: 'Doe',\n  email: 'john.doe@example.com',\n);\nfinal createdPerson = await client.crm.createPerson(newPerson);\n```\n\n### Working with Billing\n\n```dart\n// Get available plans\nfinal plansResponse = await client.billing.getPlans();\nfor (final plan in plansResponse.items) {\n  print('${plan.name}: \\$${plan.amount} per ${plan.billingTerm?.toLowerCase()}');\n}\n\n// Get a specific subscription\nfinal subscription = await client.billing.getSubscription('subscription-uid');\nprint('Subscription status: ${subscription.status}');\n```\n\n### Working with Marketing\n\n```dart\n// Get email lists\nfinal listsResponse = await client.marketing.getLists(limit: 20);\nfor (final list in listsResponse.items) {\n  print('${list.name}: ${list.subscriberCount} subscribers');\n}\n\n// Create a new email list\nfinal newList = EmailList(\n  name: 'Newsletter Subscribers',\n  description: 'People who want to receive our monthly newsletter',\n);\nfinal createdList = await client.marketing.createList(newList);\n\n// Add a subscriber to the list\nawait client.marketing.addSubscriber(\n  createdList.uid!,\n  'person-uid-here',\n);\n\n// Create and schedule an email campaign\nfinal emailCampaign = Email(\n  subject: 'Monthly Newsletter - January',\n  fromName: 'Your Company',\n  fromEmail: 'newsletter@example.com',\n  content: '\u003ch1\u003eJanuary Newsletter\u003c/h1\u003e\u003cp\u003eHere are our updates...\u003c/p\u003e',\n  emailListUid: createdList.uid,\n);\nfinal createdEmail = await client.marketing.createEmail(emailCampaign);\n\n// Schedule the email to be sent\nfinal tomorrow = DateTime.now().add(Duration(days: 1));\nawait client.marketing.scheduleEmail(createdEmail.uid!, tomorrow);\n```\n\n### Managing Support Tickets\n\n```dart\n// Create a support ticket\nfinal ticket = Ticket(\n  subject: 'Help needed',\n  description: 'I need help with my account',\n  priority: 'Medium',\n);\nfinal createdTicket = await client.support.createTicket(ticket);\n\n// Add a comment to a ticket\nawait client.support.addComment(\n  createdTicket.uid!,\n  'This is a comment',\n  isPrivate: true,\n);\n\n// Change the status of a ticket\nawait client.support.changeStatus(createdTicket.uid!, 'In Progress');\n\n// Assign a ticket to a support agent\nawait client.support.assignTicket(createdTicket.uid!, 'support-agent-uid');\n\n// Get tickets assigned to a specific person\nfinal assignedTickets = await client.support.getTicketsAssignedToPerson(\n  'support-agent-uid',\n  limit: 10,\n);\nprint('Found ${assignedTickets.metadata.total} assigned tickets');\n```\n\n### Working with User Profiles\n\n```dart\n// Get the current user's profile\nfinal currentProfile = await client.userProfile.getCurrentUser();\nprint('Logged in as: ${currentProfile.email}');\n\n// Update a user's profile\nfinal updatedProfile = currentProfile.copyWith(\n  firstName: 'New First Name',\n);\nawait client.userProfile.updateCurrentUser(updatedProfile);\n\n// Change a user's password\nawait client.userProfile.changePassword(\n  'current-password',\n  'new-password',\n  'new-password',\n);\n\n// Request a password reset for a user\nawait client.userProfile.requestPasswordReset('user@example.com');\n\n// Get an access token for client-side authentication\nfinal token = await client.userProfile.getAccessToken(\n  'username@example.com',\n  'password',\n);\nprint('Access token: $token');\n\n// Update a profile picture (base64 encoded image)\nfinal base64Image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhE...';\nawait client.userProfile.updateProfilePicture(base64Image);\n```\n\n## Additional Information\n\n### Authentication\n\nOutseta supports two authentication methods:\n\n1. **API Key Authentication**: For server-side applications. Create API keys in your Outseta account under Settings \u003e Integrations \u003e API Keys.\n\n2. **Bearer Token Authentication**: For client-side applications. Get a token by using the `getAuthToken` function or through the UserProfileApi.\n\n### Pagination\n\nAll list endpoints return paginated responses with metadata:\n\n```dart\nfinal response = await client.crm.getPeople(offset: 0, limit: 10);\nprint('Total people: ${response.metadata.total}');\nprint('Current page size: ${response.metadata.count}');\nprint('Offset: ${response.metadata.offset}');\n```\n\n### Error Handling\n\nThe client provides typed exceptions for different error scenarios:\n\n```dart\ntry {\n  await client.crm.getPerson('invalid-uid');\n} on NotFoundException catch (e) {\n  print('Person not found: ${e.message}');\n} on UnauthorizedException catch (e) {\n  print('Authentication error: ${e.message}');\n} on ApiException catch (e) {\n  print('API error: ${e.message}');\n}\n```\n\n## API Reference\n\n### Billing API\n\n| Method | Description | Parameters |\n|--------|-------------|------------|\n| `getPlans()` | Get a paginated list of plans | `offset`, `limit`, `filter` |\n| `getPlan()` | Get a plan by UID | `uid` |\n| `createPlan()` | Create a new plan | `plan` |\n| `updatePlan()` | Update an existing plan | `plan` |\n| `deletePlan()` | Delete a plan | `uid` |\n| `getSubscriptions()` | Get a paginated list of subscriptions | `offset`, `limit`, `filter` |\n| `getSubscription()` | Get a subscription by UID | `uid` |\n| `createSubscription()` | Create a new subscription | `subscription` |\n| `updateSubscription()` | Update an existing subscription | `subscription` |\n| `cancelSubscription()` | Cancel a subscription | `uid`, `cancellationReason` |\n| `getInvoices()` | Get a paginated list of invoices | `offset`, `limit`, `filter` |\n| `getInvoice()` | Get an invoice by UID | `uid` |\n| `createInvoice()` | Create a new invoice | `invoice` |\n| `updateInvoice()` | Update an existing invoice | `invoice` |\n| `markInvoiceAsPaid()` | Mark an invoice as paid | `uid` |\n| `getPayments()` | Get a paginated list of payments | `offset`, `limit`, `filter` |\n| `getPayment()` | Get a payment by UID | `uid` |\n| `createPayment()` | Create a new payment | `payment` |\n| `refundPayment()` | Refund a payment | `uid`, `amount` |\n\n### CRM API\n\n| Method | Description | Parameters |\n|--------|-------------|------------|\n| `getPeople()` | Get a paginated list of people | `offset`, `limit`, `filter` |\n| `getPerson()` | Get a person by UID | `uid` |\n| `createPerson()` | Create a new person | `person` |\n| `updatePerson()` | Update an existing person | `person` |\n| `deletePerson()` | Delete a person | `uid` |\n| `getAccounts()` | Get a paginated list of accounts | `offset`, `limit`, `filter` |\n| `getAccount()` | Get an account by UID | `uid` |\n| `createAccount()` | Create a new account | `account` |\n| `updateAccount()` | Update an existing account | `account` |\n| `deleteAccount()` | Delete an account | `uid` |\n| `addPersonToAccount()` | Add a person to an account | `accountUid`, `personUid` |\n| `removePersonFromAccount()` | Remove a person from an account | `accountUid`, `personUid` |\n| `getDeals()` | Get a paginated list of deals | `offset`, `limit`, `filter` |\n| `getDeal()` | Get a deal by UID | `uid` |\n| `createDeal()` | Create a new deal | `deal` |\n| `updateDeal()` | Update an existing deal | `deal` |\n| `deleteDeal()` | Delete a deal | `uid` |\n\n### Marketing API\n\n| Method | Description | Parameters |\n|--------|-------------|------------|\n| `getLists()` | Get a paginated list of email lists | `offset`, `limit`, `filter` |\n| `getList()` | Get an email list by UID | `uid` |\n| `createList()` | Create a new email list | `list` |\n| `updateList()` | Update an existing email list | `list` |\n| `deleteList()` | Delete an email list | `uid` |\n| `getSubscribers()` | Get subscribers for a list | `listUid`, `offset`, `limit`, `filter` |\n| `addSubscriber()` | Add a subscriber to a list | `listUid`, `personUid` |\n| `removeSubscriber()` | Remove a subscriber from a list | `listUid`, `personUid` |\n| `getEmails()` | Get a paginated list of email campaigns | `offset`, `limit`, `filter` |\n| `getEmail()` | Get an email campaign by UID | `uid` |\n| `createEmail()` | Create a new email campaign | `email` |\n| `updateEmail()` | Update an existing email campaign | `email` |\n| `deleteEmail()` | Delete an email campaign | `uid` |\n| `sendTestEmail()` | Send a test email | `emailUid`, `recipientEmail` |\n| `scheduleEmail()` | Schedule an email campaign | `emailUid`, `scheduledDate` |\n| `sendEmailNow()` | Send an email campaign immediately | `emailUid` |\n| `cancelScheduledEmail()` | Cancel a scheduled email campaign | `emailUid` |\n\n### Support API\n\n| Method | Description | Parameters |\n|--------|-------------|------------|\n| `getTickets()` | Get a paginated list of tickets | `offset`, `limit`, `filter` |\n| `getTicket()` | Get a ticket by UID | `uid` |\n| `createTicket()` | Create a new ticket | `ticket` |\n| `updateTicket()` | Update an existing ticket | `ticket` |\n| `deleteTicket()` | Delete a ticket | `uid` |\n| `addComment()` | Add a comment to a ticket | `ticketUid`, `comment`, `isPrivate` |\n| `changeStatus()` | Change the status of a ticket | `ticketUid`, `status` |\n| `assignTicket()` | Assign a ticket to a person | `ticketUid`, `personUid` |\n| `getTicketsAssignedToPerson()` | Get tickets assigned to a person | `personUid`, `offset`, `limit` |\n| `getTicketsSubmittedByPerson()` | Get tickets submitted by a person | `personUid`, `offset`, `limit` |\n\n### User Profile API\n\n| Method | Description | Parameters |\n|--------|-------------|------------|\n| `getCurrentUser()` | Get the current user's profile | none |\n| `updateCurrentUser()` | Update the current user's profile | `profile` |\n| `changePassword()` | Change the current user's password | `currentPassword`, `newPassword`, `confirmPassword` |\n| `requestPasswordReset()` | Request a password reset for a user | `email` |\n| `resetPassword()` | Reset a user's password using a token | `token`, `newPassword`, `confirmPassword` |\n| `getAccessToken()` | Get an access token for client-side auth | `username`, `password` |\n| `verifyEmail()` | Verify an email address | `token` |\n| `requestEmailVerification()` | Request a new email verification token | `email` |\n| `updateProfilePicture()` | Update the user's profile picture | `base64Image` |\n\n## Testing\n\nThe package includes comprehensive tests for all API modules:\n\n```bash\n# Run all tests\ndart run test\n\n# Run tests with coverage\ndart run test --coverage=coverage\n```\n\n### Integration Tests\n\nSome functionality, like the `getAuthToken()` function, requires actual API credentials and network connections. These tests are separate from the main test suite:\n\n```bash\n# Create a .env file with your Outseta credentials\n# OUTSETA_BASE_URL=https://your-domain.outseta.com/api/v1\n# OUTSETA_USERNAME=your-username\n# OUTSETA_PASSWORD=your-password\n\n# Run integration tests\ndart test --tags=integration integration_test/\n```\n\n### Test Coverage\n\nThe package includes comprehensive tests with high code coverage for all components:\n\n- **Authentication**: API Key and Bearer Token authentication methods\n- **Exception Handling**: All exception types and error scenarios\n- **OutsetaClient**: HTTP methods (GET, POST, PUT, DELETE) and error handling\n- **CRM API**: People, accounts, and deals operations\n- **Billing API**: Plans, subscriptions, invoices, and payments management\n- **Marketing API**: Lists, subscribers, and email campaigns\n- **Support API**: Tickets, comments, and ticket management\n- **User Profile API**: User profile management and authentication\n- **Models**: Serialization/deserialization, equality comparison, and copying\n\n\u003e **Note**: Some functions, like `getAuthToken()`, are excluded from coverage metrics as they require actual API connections. See `test/coverage_exclusions/README.md` for details on these exclusions.\n\nEach API module has tests for:\n- Retrieving paginated collections\n- Getting individual resources\n- Creating and updating resources\n- Special operations specific to that module\n- Error handling and validation\n\n### Viewing Test Coverage\n\nTo view the coverage report:\n\n```bash\n# Install coverage tools if you haven't already\ndart pub global activate coverage\n\n# Installing lcov (required for HTML reports)\n# On macOS\nbrew install lcov\n\n# On Ubuntu/Debian\nsudo apt-get install lcov\n\n# On Windows (using Chocolatey)\nchoco install lcov\n\n# Run tests with coverage\ndart run test --coverage=coverage\n\n# Process the coverage report (excluding generated files)\ndart pub global run coverage:format_coverage --lcov --in=coverage --out=coverage/lcov.info --report-on=lib/ --exclude-files=\"lib/src/generated/**,**/*.g.dart\"\n\n# Generate HTML report (requires lcov)\ngenhtml -o coverage/html coverage/lcov.info\n\n# Open the HTML report\nopen coverage/html/index.html  # On macOS\nxdg-open coverage/html/index.html  # On Linux\nstart coverage/html/index.html  # On Windows\n```\n\nThe `dart_test.yaml` file in the project root includes coverage configurations that exclude functions that can't be properly unit tested, such as those requiring actual network connections.\n\nFor VS Code users, you can also use the \"Dart Code Coverage\" extension to visualize coverage directly in your editor.\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n## Copyright\n\nCopyright (c) 2025 Dom Jocubeit\n\n## License\n\nThis project is licensed under the MIT License - see the LICENSE file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdartfoundry%2Foutseta","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdartfoundry%2Foutseta","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdartfoundry%2Foutseta/lists"}