Estimates

List, create, update, and delete estimates; status transitions; email and PDF actions; standalone estimate items; company default additional text.

   
SDK service client.Estimates (EstimateService)
FreeAgent docs dev.freeagent.com/docs/estimates
Sample app /estimates (list), /estimates/detail (CRUD and transitions)

Pagination patterns: see Pagination. Contact, project, category, and invoice links: see Linked resources. Sales cluster patterns: see Sales entity map.

Methods

ListAsync

Task<PaginatedResponse<Estimate>> ListAsync(
    int page = 1,
    int perPage = 25,
    string? view = null,
    DateOnly? fromDate = null,
    DateOnly? toDate = null,
    DateTimeOffset? updatedSince = null,
    ContactReference? contact = null,
    long? contactId = null,
    ProjectReference? project = null,
    long? projectId = null,
    InvoiceReference? invoice = null,
    long? invoiceId = null,
    bool? nestedEstimateItems = null,
    CancellationToken cancellationToken = default)
Parameter Type Required Default Wire
page int No 1 page
perPage int No 25 per_page (max 100)
view string? No null view
fromDate DateOnly? No null from_date
toDate DateOnly? No null to_date
updatedSince DateTimeOffset? No null updated_since
contact ContactReference? No null contact (URI)
contactId long? No null contact (URI from client.Urls.Contact(id))
project ProjectReference? No null project (URI)
projectId long? No null project (URI from client.Urls.Project(id))
invoice InvoiceReference? No null invoice (URI)
invoiceId long? No null invoice (URI from client.Urls.Invoice(id))
nestedEstimateItems bool? No null nested_estimate_items
cancellationToken CancellationToken No default -

HTTP: GET /v2/estimates

Client validation: page ≥ 1; perPage between 1 and 100; supply contact or contactId, not both; supply project or projectId, not both; supply invoice or invoiceId, not both.

Sample:

using FreeAgent.Client.Models.Estimates;

var page = await client.Estimates.ListAsync(
    view: EstimateViews.Draft,
    contactId: 42,
    nestedEstimateItems: true);

ListAutoPagingAsync

IAsyncEnumerable<Estimate> ListAutoPagingAsync(...)

Same query parameters as ListAsync except page.


GetEstimateAsync

Task<Estimate> GetEstimateAsync(long estimateId, CancellationToken cancellationToken = default)

Task<Estimate> GetEstimateAsync(
    long estimateId,
    EstimateGetOptions? options,
    CancellationToken cancellationToken = default)

HTTP: GET /v2/estimates/:id

When options.IncludeContact or options.IncludeProject is true, the SDK fetches linked resources when the response contains only URIs.


GetEstimatePdfAsync

Task<byte[]> GetEstimatePdfAsync(long estimateId, CancellationToken cancellationToken = default)

HTTP: GET /v2/estimates/:id/pdf

Decodes the base64 pdf.content wrapper returned by FreeAgent.


CreateEstimateAsync

Task<Estimate> CreateEstimateAsync(Estimate estimate, CancellationToken cancellationToken = default)

HTTP: POST /v2/estimates

Required on create: ContactId, DatedOn, EstimateType, and at least one line item with Description. FreeAgent also requires status on create; the SDK always sends Draft (ignoring any Status on the input model). Use transition methods to change status after create.


DuplicateEstimateAsync

Task<Estimate> DuplicateEstimateAsync(long estimateId, CancellationToken cancellationToken = default)

HTTP: POST /v2/estimates/:id/duplicate


UpdateEstimateAsync

Task<Estimate> UpdateEstimateAsync(
    long estimateId,
    Estimate estimate,
    EstimateUpdateOptions? options = null,
    CancellationToken cancellationToken = default)

HTTP: PUT /v2/estimates/:id

Update existing line items with ItemId. Delete line items with ItemId and Destroy = 1. Pass new EstimateUpdateOptions { OmitLineItems = true } to update scalar fields without sending line items.


DeleteEstimateAsync

Task DeleteEstimateAsync(long estimateId, CancellationToken cancellationToken = default)

HTTP: DELETE /v2/estimates/:id


CreateEstimateItemAsync

Task<EstimateItem> CreateEstimateItemAsync(
    long estimateId,
    EstimateItem estimateItem,
    CancellationToken cancellationToken = default)

HTTP: POST /v2/estimate_items

Creates a line item on an existing estimate. The request envelope includes both the parent estimate URI and the item attributes.


UpdateEstimateItemAsync

Task<EstimateItem> UpdateEstimateItemAsync(
    long estimateItemId,
    EstimateItem estimateItem,
    CancellationToken cancellationToken = default)

HTTP: PUT /v2/estimate_items/:id


DeleteEstimateItemAsync

Task DeleteEstimateItemAsync(long estimateItemId, CancellationToken cancellationToken = default)

HTTP: DELETE /v2/estimate_items/:id


SendEstimateEmailAsync

Task SendEstimateEmailAsync(
    long estimateId,
    SendEstimateEmailRequest request,
    CancellationToken cancellationToken = default)

HTTP: POST /v2/estimates/:id/send_email

Set request.Email.UseTemplate = true to send using an existing template.


MarkEstimateAsSentAsync / MarkEstimateAsDraftAsync / MarkEstimateAsApprovedAsync / MarkEstimateAsRejectedAsync

Task<Estimate> MarkEstimateAsSentAsync(long estimateId, CancellationToken cancellationToken = default)
Task<Estimate> MarkEstimateAsDraftAsync(long estimateId, CancellationToken cancellationToken = default)
Task<Estimate> MarkEstimateAsApprovedAsync(long estimateId, CancellationToken cancellationToken = default)
Task<Estimate> MarkEstimateAsRejectedAsync(long estimateId, CancellationToken cancellationToken = default)

HTTP: PUT /v2/estimates/:id/transitions/{action}

Behaviour: The SDK sends PUT .../transitions/{action} with an empty JSON body ({}), then fetches the updated estimate with GET .../estimates/:id because FreeAgent transition endpoints return no response body. If the transition succeeds but the follow-up GET fails, the estimate state has already changed on the server; retry the GET to obtain the updated resource.


ConvertToInvoiceAsync

Task<Estimate> ConvertToInvoiceAsync(long estimateId, CancellationToken cancellationToken = default)

HTTP: PUT /v2/estimates/:id/transitions/convert_to_invoice

Returns the updated estimate with an invoice link populated. When the transition response has no estimate body, the SDK fetches the estimate with GET .../estimates/:id.


GetDefaultAdditionalTextAsync / UpdateDefaultAdditionalTextAsync / DeleteDefaultAdditionalTextAsync

HTTP: GET, PUT, and DELETE /v2/estimates/default_additional_text

Company-level default text shown on all estimates.

List filters

view (EstimateViews)

Constant Wire value
EstimateViews.All all
EstimateViews.Recent recent
EstimateViews.Draft draft
EstimateViews.NonDraft non_draft
EstimateViews.Sent sent
EstimateViews.Approved approved
EstimateViews.Rejected rejected
EstimateViews.Invoiced invoiced

EstimateGetOptions

Property Type Purpose
IncludeContact bool Fetch contact when only a URI is returned
IncludeProject bool Fetch project when only a URI is returned

EstimateUpdateOptions

Property Type Purpose
OmitLineItems bool Exclude line items from the update payload
OmitContact bool Exclude the contact link from the update payload
OmitProject bool Exclude the project link from the update payload

Estimate model

Key properties:

Property Type Wire name Notes
Reference string? reference  
Status EstimateStatus? status Read-only on create (SDK always sends Draft); not sent on update — use transition methods
EstimateType EstimateType? estimate_type Required on create
DatedOn DateOnly? dated_on Required on create
ContactId long? contact Required on create
ProjectId long? project Optional project link
EstimateItems List<EstimateItem>? estimate_items Nested line items
ItemId long? id (line items) Required to update or delete existing line items
CategoryNominalCode string? category (line items) Category link on line items
SalesTaxStatus InvoiceSalesTaxStatus? sales_tax_status Read-only on list/get responses; set per line item on create/update
SalesTaxValue decimal? sales_tax_value Read-only total from line items
Url string url  

Errors

Failures throw FreeAgentApiException.


Copyright © Mark Heydon. Distributed under the MIT licence.

This site uses Just the Docs, a documentation theme for Jekyll.