Avoice
On this page
API Reference

Avoice API

The Avoice API lets a practice take a complete, scheduled copy of everything it holds in Avoice: specifications and their full revision history, materials, schedules, uploaded documents, drawings, generated outputs, QA and contract administration records, and the metadata that ties them together.

The API is organised around REST. It accepts and returns JSON, uses standard HTTP response codes and bearer authentication, and is served over HTTPS only. Version 1 is read-only and covers:

  • Specifications: every specification, its sections and clauses, TBC flags, and each issued revision.
  • Product library: your practice's products, categories, and suppliers, and the products selected on each project.
  • Schedules: finishes, FF&E, door, and other schedules on a project.
  • Exports: a complete backup of the whole practice as a downloadable archive, for scheduled backups.

Every request is scoped to a single practice. There is no test mode; the API is read-only, so it is safe to use against live data.

Base URL
https://api.avoice.co/v1
Your first requestcurl
curl https://api.avoice.co/v1/exports \
  -H "Authorization: Bearer avk_live_…"

Authentication

Authenticate with a practice API key sent as a bearer token in the Authorization header. Requests without a valid key return 401.

  • Keys are created and revoked by practice admins under Settings → Integrations → API keys. A key belongs to the practice, not to a person, so it keeps working when staff change.
  • A key is shown once, when it is created. Avoice stores only a hash. Keys start with avk_live_ so secret scanners can recognise them.
  • Each key carries only the permissions you give it: read:specifications, read:materials (product library), read:schedules, read:projects, and read:exports. Use a separate key for each integration.
  • To rotate, create a new key, update your script, then revoke the old key. Both work during the overlap. Revocation takes effect immediately.

Key creation, revocation, and every export started or downloaded are recorded in the practice audit log with the key name, IP address, and time.

Authenticated requestcurl
curl https://api.avoice.co/v1/exports \
  -H "Authorization: Bearer avk_live_3kP9…Wq7x"

Errors

Avoice uses conventional HTTP status codes. Codes in the 2xx range mean success, 4xx means the request could not be completed as sent, and 5xx means something went wrong on our side. Every response carries a Request-Id header; include it when you contact support.

StatusMeaning
200 OKThe request succeeded.
202 AcceptedThe export job was queued.
302 FoundRedirect to a signed archive download URL.
400 Bad RequestA parameter is missing or invalid.
401 UnauthorizedNo valid API key was provided.
403 ForbiddenThe key lacks the permission this endpoint needs.
404 Not FoundThe resource does not exist in this practice. IDs from other practices always return 404.
409 ConflictThe export is not in a state that allows this action.
410 GoneThe archive has expired and been deleted.
429 Too Many RequestsA rate limit was hit. Wait for Retry-After seconds.
500, 503An error on Avoice's side. Safe to retry with backoff.

Error attributes

typestring
The category of error.invalid_request_errorauthentication_errorpermission_errorrate_limit_errorapi_error
codestring
A stable, machine-readable code such as export_not_ready or archive_expired. Safe to branch on.
messagestring
A human-readable explanation. May change; do not parse it.
paramstring, nullable
The parameter the error relates to, if any.
request_idstring
Matches the Request-Id response header.
Error responsejson
{
  "error": {
    "type": "invalid_request_error",
    "code": "project_not_found",
    "message": "No project with id 'clx8r1…' exists in this practice.",
    "param": "scope.project_ids",
    "request_id": "req_7Hq2LmZ0c8Vb"
  }
}

Rate limits

Limits protect the service rather than meter your usage. A nightly full backup always fits comfortably within them. When a limit is reached the API returns 429 with a Retry-After header; back off for that many seconds rather than retrying immediately.

LimitValue
Concurrent exports1 running per practice, up to 5 queued
Exports started4 per practice per day
Other requests100 per minute per key
Archive sizeNo cap. Archives over 20 GB are split into numbered parts
Download link lifetime24 hours
Archive retention7 days after completion
Job timeout6 hours, then failed with a reason

Enterprise practices can have these limits raised. Contact us with your practice name and the volume you need.

429 response headershttp
HTTP/1.1 429 Too Many Requests
Retry-After: 120
RateLimit-Limit: 4
RateLimit-Remaining: 0
RateLimit-Reset: 41880

Pagination

List endpoints return results newest first, using cursor-based pagination. Pass limit to set the page size and starting_after with the last ID you received to fetch the next page. Keep going while has_more is true.

Parameters

limitinteger
Number of objects to return, between 1 and 100. Defaults to 10.
starting_afterstring
An object ID. Returns the page of results after this object.
ending_beforestring
An object ID. Returns the page of results before this object.
List responsejson
{
  "object": "list",
  "url": "/v1/exports",
  "has_more": true,
  "data": [
    { "id": "exp_01J8ZK…", "object": "export", … },
    { "id": "exp_01J8XH…", "object": "export", … }
  ]
}

Versioning

The API version is part of the path (/v1). Additive changes, such as new fields, new optional parameters, or new object types in the archive, can ship at any time, so write your integration to ignore fields it does not recognise.

The archive format has its own date-based schema version (2026-09), recorded in every manifest. Breaking changes to the archive get a new schema version, and the previous version continues to be available for at least 12 months.

Specifications

The Specification object

A Specification is one specification document on a project, written to a single standard. It holds sections, each section holds clauses, and every issue of the document is kept as a publication. Specification endpoints need the read:specifications permission.

Attributes

idstring
Unique identifier. The same ID is used in the app and in exports.
objectstring
Always "specification".
project_idstring
The project the specification belongs to.
namestring
The document name, such as Architectural Specification.
standardenum
The specification standard and numbering system.cawsuniclassnatspeccsi
scopestring
What the document covers, for example combined or a single package.
section_count, tbc_countinteger
Number of sections, and of clauses still flagged TBC.
latest_publicationobject, nullable
Number, revision, purpose, and date of the most recent issue.
archived_attimestamp, nullable
Set when the specification has been archived.
created_at, updated_attimestamp
When the specification was created and last changed.
The Specification objectjson
{
  "id": "clz9p3w7n0001ht6y2u8io4pa",
  "object": "specification",
  "project_id": "clx8r1k5t0006zx8c7v6bn5ma",
  "name": "Architectural Specification",
  "standard": "caws",
  "scope": "combined",
  "section_count": 64,
  "tbc_count": 7,
  "latest_publication": {
    "id": "clzb0e4r1000ajk7l3z5xc9vb",
    "number": "SPEC-001",
    "revision": "C",
    "issued_for": "Tender",
    "issued_at": "2026-09-15T16:02:44Z"
  },
  "archived_at": null,
  "created_at": "2026-05-06T10:12:30Z",
  "updated_at": "2026-09-19T08:47:02Z"
}

List all specifications

GET/v1/specifications

Returns the specifications in your practice, most recently updated first. Supports pagination.

Parameters

projectstring
Only specifications on this project.
standardenum
Only specifications written to this standard.cawsuniclassnatspeccsi
include_archivedboolean
Include archived specifications. Defaults to false.
updated[gte]timestamp
Only specifications changed at or after this time.
GET/v1/specificationscurl
curl -G https://api.avoice.co/v1/specifications \
  -H "Authorization: Bearer avk_live_…" \
  -d project=clx8r1k5t0006zx8c7v6bn5ma

Retrieve a specification

GET/v1/specifications/:id

Returns a single specification with its summary counts and latest issue. Use List sections for the content.

GET/v1/specifications/:idcurl
curl https://api.avoice.co/v1/specifications/clz9p3w7n0001ht6y2u8io4pa \
  -H "Authorization: Bearer avk_live_…"

List sections and clauses

GET/v1/specifications/:id/sections

Returns the sections of a specification in document order. Pass include=clausesto get each section's clauses with their numbering, text, TBC flags, and linked products.

Parameters

includeenum
Add the clauses to each section.clauses
codestring
Only the section with this code, such as M50 or Ss_25_10_20.
statusenum
Only sections at this stage.emptydraftingfilledreviewed
has_tbcboolean
Only sections with at least one clause flagged TBC.
GET/v1/specifications/:id/sectionscurl
curl -G https://api.avoice.co/v1/specifications/clz9p3…/sections \
  -H "Authorization: Bearer avk_live_…" \
  -d include=clauses \
  -d code=M50
Response · 200 OKjson
{
  "object": "list",
  "url": "/v1/specifications/clz9p3…/sections",
  "has_more": false,
  "data": [
    {
      "id": "clzc7h2k9000bnm4q8w1er6ty",
      "object": "spec_section",
      "code": "M50",
      "name": "Timber strip/board fine flooring and linings",
      "division": "M",
      "status": "reviewed",
      "version": 6,
      "updated_at": "2026-09-12T13:21:09Z",
      "clauses": [
        {
          "number": "M50/110",
          "title": "Engineered wood flooring",
          "body": "Manufacturer: Northfield Timber Co.\nProduct reference: NF-OAK-190-NB\nFinish: UV oiled…",
          "tbc": false,
          "product_ids": ["clz4h8q2m0003kd9x7v1bt5ne"]
        },
        {
          "number": "M50/120",
          "title": "Underlay",
          "body": "Thickness: [TBC — confirm with acoustic engineer]…",
          "tbc": true,
          "tbc_reasons": ["confirm with acoustic engineer"],
          "product_ids": []
        }
      ]
    }
  ]
}

List publications

GET/v1/specifications/:id/publications

Returns every issued revision of the specification, newest first: its number, revision letter, purpose of issue, the sections it covered, and a signed link to the PDF or DOCX that was issued. Links are valid for 24 hours.

GET/v1/specifications/:id/publicationscurl
curl https://api.avoice.co/v1/specifications/clz9p3…/publications \
  -H "Authorization: Bearer avk_live_…"
Response · 200 OKjson
{
  "object": "list",
  "url": "/v1/specifications/clz9p3…/publications",
  "has_more": false,
  "data": [
    {
      "id": "clzb0e4r1000ajk7l3z5xc9vb",
      "object": "spec_publication",
      "number": "SPEC-001",
      "revision": "C",
      "issued_for": "Tender",
      "version": 3,
      "format": "pdf",
      "section_codes": ["F10", "K10", "M50", "…"],
      "published_by": { "id": "clx2u7…", "name": "Priya Shah" },
      "file": {
        "name": "SPEC-001 Rev C - Tender.pdf",
        "size_bytes": 2841120,
        "url": "https://files.avoice.co/publications/clzb0e…?sig=…",
        "expires_at": "2026-09-24T09:00:00Z"
      },
      "compared_to_id": "clza8m…",
      "created_at": "2026-09-15T16:02:44Z"
    }
  ]
}
Product library

The Product object

A Product is one entry in your practice's product library: a material, finish, fitting, or system your team has researched and recorded, with its manufacturer, category, technical properties, documents, and suppliers. Product library endpoints need the read:materials permission.

Attributes

idstring
Unique identifier. The same ID is used in the app and in exports.
objectstring
Always "product".
namestring
The product name as it appears in your library.
statusenum
Lifecycle status in the library. Drafts are only returned when you filter for them.activediscontinuedarchiveddraft
manufacturerobject, nullable
The manufacturer's supplier record: id and name.
skustring, nullable
The manufacturer's product code.
internal_codestring, nullable
Your practice's own reference, such as FL-12.
categoryobject, nullable
The product category: id, code (for example a Uniclass Pr code), and name.
subcategoryobject, nullable
The subcategory within the category.
attributesobject
Values for the category's attributes, keyed by attribute. See List product categories for the definitions.
applicationarray of strings
Where the product is used, such as floor, wall, or ceiling.
interior, exteriorboolean
Whether the product is suitable for internal and external use.
dimensions, performance, sustainabilityobject
Structured technical data recorded for the product. Keys vary by category.
documentsarray
Datasheets, certificates, and other documents, each with type, name, and url.
suppliersarray
Suppliers you buy the product through, with the primary supplier marked.
created_at, updated_attimestamp
When the product was added and last changed.
The Product objectjson
{
  "id": "clz4h8q2m0003kd9x7v1bt5ne",
  "object": "product",
  "name": "Engineered oak flooring, Nordic Blonde, 190 mm",
  "status": "active",
  "manufacturer": {
    "id": "clx7a2k1f0001pl3m9q8wr4ty",
    "name": "Northfield Timber Co."
  },
  "sku": "NF-OAK-190-NB",
  "internal_code": "FL-12",
  "category": {
    "id": "clx7c0d9e0002ab4c5d6e7f8g",
    "code": "Pr_35_57_06",
    "name": "Timber flooring"
  },
  "subcategory": { "id": "clx7c1…", "name": "Engineered boards" },
  "attributes": {
    "finish": "UV oiled",
    "wear_layer_mm": "3.5",
    "reaction_to_fire": "Cfl-s1"
  },
  "application": ["floor"],
  "color_family": "natural",
  "interior": true,
  "exterior": false,
  "image_url": "https://files.avoice.co/products/clz4h8…/main.jpg",
  "summary": "Three-layer engineered oak board, suitable for underfloor heating.",
  "dimensions": { "length_mm": 1860, "width_mm": 190, "thickness_mm": 14 },
  "sustainability": { "fsc_certified": true, "epd_url": "https://…" },
  "documents": [
    { "type": "datasheet", "name": "Technical datasheet", "url": "https://…" }
  ],
  "suppliers": [
    { "id": "clx7s9…", "name": "Hartley Flooring Supplies", "primary": true }
  ],
  "created_at": "2026-03-11T09:42:18Z",
  "updated_at": "2026-09-02T14:05:51Z"
}

List all products

GET/v1/products

Returns the products in your library, most recently updated first. Supports pagination. To keep another system in sync, pass updated[gte] with the time of your last sync.

Parameters

qstring
Search product name, manufacturer, SKU, and internal code.
categorystring
A category ID or code.
subcategorystring
A subcategory ID.
manufacturerstring
A manufacturer's supplier ID.
supplierstring
Only products available through this supplier.
statusenum
Defaults to active.activediscontinuedarchiveddraft
updated[gte]timestamp
Only products changed at or after this time.
GET/v1/productscurl
curl -G https://api.avoice.co/v1/products \
  -H "Authorization: Bearer avk_live_…" \
  -d category=Pr_35_57_06 \
  -d "updated[gte]=2026-09-01T00:00:00Z" \
  -d limit=50
Response · 200 OKjson
{
  "object": "list",
  "url": "/v1/products",
  "has_more": true,
  "data": [
    {
      "id": "clz4h8q2m0003kd9x7v1bt5ne",
      "object": "product",
      "name": "Engineered oak flooring, Nordic Blonde, 190 mm",
      "status": "active",
      "category": { "code": "Pr_35_57_06", "name": "Timber flooring" },
      …
    },
    …
  ]
}

Retrieve a product

GET/v1/products/:id

Returns a single product with all of its attributes, documents, and suppliers. Products belonging to another practice return 404.

GET/v1/products/:idcurl
curl https://api.avoice.co/v1/products/clz4h8q2m0003kd9x7v1bt5ne \
  -H "Authorization: Bearer avk_live_…"

List product categories

GET/v1/product_categories

Returns your practice's product categories with their subcategories and the attribute definitions that apply to products in each category. Use it to interpret a product's attributes and to build filters.

GET/v1/product_categoriescurl
curl https://api.avoice.co/v1/product_categories \
  -H "Authorization: Bearer avk_live_…"
Response · 200 OKjson
{
  "object": "list",
  "url": "/v1/product_categories",
  "has_more": false,
  "data": [
    {
      "id": "clx7c0d9e0002ab4c5d6e7f8g",
      "object": "product_category",
      "code": "Pr_35_57_06",
      "name": "Timber flooring",
      "group": "Finishes",
      "subcategories": [
        { "id": "clx7c1…", "name": "Engineered boards", "code": null },
        { "id": "clx7c2…", "name": "Solid boards", "code": null }
      ],
      "attributes": [
        { "key": "finish", "label": "Finish", "type": "text" },
        { "key": "reaction_to_fire", "label": "Reaction to fire", "type": "text" }
      ]
    }
  ]
}

List suppliers

GET/v1/suppliers

Returns the manufacturers and suppliers in your library with their contacts. Supports pagination.

Parameters

company_typeenum
Filter by the kind of company.manufacturersupplierboth
qstring
Search by company name.
GET/v1/supplierscurl
curl -G https://api.avoice.co/v1/suppliers \
  -H "Authorization: Bearer avk_live_…" \
  -d company_type=manufacturer
Response · 200 OKjson
{
  "object": "list",
  "url": "/v1/suppliers",
  "has_more": false,
  "data": [
    {
      "id": "clx7s9f3a0004qw2e1r5ty8ui",
      "object": "supplier",
      "name": "Hartley Flooring Supplies",
      "company_type": "supplier",
      "website": "https://…",
      "product_count": 214,
      "contacts": [
        { "name": "Sam Hartley", "email": "sam@…", "role": "Account manager" }
      ]
    }
  ]
}

List a project's products

GET/v1/projects/:id/products

Returns the products selected on a project, with their status on that project, notes, who added them, and the specification sections they appear in. Needs both read:materials and read:projects.

Parameters

statusenum
Status of the product on this project. Returns all statuses by default.proposedactiverejectedsuperseded
GET/v1/projects/:id/productscurl
curl -G https://api.avoice.co/v1/projects/clx8r1k5t0006zx8c7v6bn5ma/products \
  -H "Authorization: Bearer avk_live_…" \
  -d status=active
Response · 200 OKjson
{
  "object": "list",
  "url": "/v1/projects/clx8r1…/products",
  "has_more": false,
  "data": [
    {
      "object": "project_product",
      "project_id": "clx8r1k5t0006zx8c7v6bn5ma",
      "status": "active",
      "notes": "Ground floor living areas. Client approved sample 04/09.",
      "added_at": "2026-09-04T11:20:07Z",
      "added_by": { "id": "clx2u7…", "name": "Priya Shah" },
      "spec_sections": ["M50"],
      "product": {
        "id": "clz4h8q2m0003kd9x7v1bt5ne",
        "object": "product",
        "name": "Engineered oak flooring, Nordic Blonde, 190 mm",
        …
      }
    }
  ]
}
Schedules

List a project's schedules

GET/v1/projects/:id/schedules

Returns the schedules on a project, such as finishes, FF&E, door, and lighting schedules, with their item counts. Schedule endpoints need the read:schedules permission.

Parameters

typeenum
Only schedules of this type.finishesffefurniturelightingdoorcustom
GET/v1/projects/:id/schedulescurl
curl -G https://api.avoice.co/v1/projects/clx8r1…/schedules \
  -H "Authorization: Bearer avk_live_…" \
  -d type=finishes

Retrieve a schedule

GET/v1/schedules/:id

Returns a schedule with its columns and rows. Each row is an item in an area, with its values and any products from your product library assigned to it.

GET/v1/schedules/:idcurl
curl https://api.avoice.co/v1/schedules/clz2f6d4s0008vb3n1m7qx9lk \
  -H "Authorization: Bearer avk_live_…"
Response · 200 OKjson
{
  "id": "clz2f6d4s0008vb3n1m7qx9lk",
  "object": "schedule",
  "project_id": "clx8r1k5t0006zx8c7v6bn5ma",
  "name": "Internal finishes",
  "type": "finishes",
  "columns": ["Area", "Element", "Product", "Finish", "Notes"],
  "rows": [
    {
      "id": "clz2g1…",
      "area": "Ground floor living",
      "element": "Floor",
      "values": { "finish": "UV oiled", "notes": "Run boards east–west" },
      "products": [
        { "id": "clz4h8q2m0003kd9x7v1bt5ne", "name": "Engineered oak flooring, Nordic Blonde, 190 mm" }
      ]
    }
  ],
  "updated_at": "2026-09-18T15:30:12Z"
}
Exports

The Export object

An Export is a background job that builds an archive of your practice data. Large practices can take a while to export, so the job runs on its own queue and never slows down Avoice for your team.

Attributes

idstring
Unique identifier, prefixed exp_.
objectstring
Always "export".
statusenum
Where the job is in its lifecycle.queuedrunningreadyfailedcanceledexpired
scopeobject
What the export covers. type is practice for everything, or projects with a list of project_ids.
sincetimestamp, nullable
If set, the archive only holds records created, changed, or deleted after this time.
include_filesboolean
Whether binary files are included alongside the structured data.
schema_versionstring
The archive schema version used for this export.
created_byobject
The API key or admin who started the export.
progressobject
Record and file counts, total and done, updated while running.
partsarray
Once ready: one entry per archive part with its number, size, and SHA-256 checksum.
expires_attimestamp, nullable
When the archive will be deleted. After this the status becomes expired.
failure_reasonstring, nullable
Set when the status is failed.
The Export objectjson
{
  "id": "exp_01J8ZK4V7Q2M9D3HX6T5RWN0BC",
  "object": "export",
  "status": "ready",
  "scope": {
    "type": "practice",
    "project_ids": null
  },
  "since": null,
  "include_files": true,
  "format": "json",
  "schema_version": "2026-09",
  "created_at": "2026-09-23T02:00:04Z",
  "started_at": "2026-09-23T02:00:09Z",
  "completed_at": "2026-09-23T02:41:37Z",
  "created_by": {
    "type": "api_key",
    "id": "key_01J7Y2B8C4M1N6P3Q5R7S9T0VW",
    "name": "Nightly backup"
  },
  "progress": {
    "records_total": 184213,
    "records_done": 184213,
    "files_total": 9412,
    "files_done": 9412
  },
  "size_bytes": 31874520113,
  "parts": [
    {
      "number": 1,
      "size_bytes": 21474836480,
      "sha256": "9f2c41d0b7e3…a61e"
    },
    {
      "number": 2,
      "size_bytes": 10399683633,
      "sha256": "0c7d88e2f415…3b90"
    }
  ],
  "expires_at": "2026-09-30T02:41:37Z",
  "failure_reason": null
}

Create an export

POST/v1/exports

Starts a new export job and returns it with status queued. Poll Retrieve an export until the status is ready.

Parameters

scopeobject
Defaults to {"type": "practice"}, the whole practice including archived projects. Use {"type": "projects", "project_ids": [...]} to export selected projects only.
sincetimestamp
ISO 8601. Export only what changed after this time, including deletions, for incremental backups.
include_filesboolean
Include uploaded files and generated documents. Defaults to true. Set false for a fast metadata-only export.
formatenum
Archive data format. Only json is available in v1.json
POST/v1/exportscurl
curl https://api.avoice.co/v1/exports \
  -H "Authorization: Bearer avk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "scope": { "type": "practice" },
    "include_files": true
  }'
Response · 202 Acceptedjson
{
  "id": "exp_01J8ZK4V7Q2M9D3HX6T5RWN0BC",
  "object": "export",
  "status": "queued",
  "scope": { "type": "practice", "project_ids": null },
  "since": null,
  "include_files": true,
  "format": "json",
  "created_at": "2026-09-23T02:00:04Z",
  …
}

Retrieve an export

GET/v1/exports/:id

Returns an export by ID, with live progress while it runs. Once the status is ready, the response includes the parts to download and their checksums. Polling once a minute is plenty.

GET/v1/exports/:idcurl
curl https://api.avoice.co/v1/exports/exp_01J8ZK4V7Q2M9D3HX6T5RWN0BC \
  -H "Authorization: Bearer avk_live_…"
Response · 200 OKjson
{
  "id": "exp_01J8ZK4V7Q2M9D3HX6T5RWN0BC",
  "object": "export",
  "status": "running",
  "progress": {
    "records_total": 184213,
    "records_done": 92740,
    "files_total": 9412,
    "files_done": 3118
  },
  …
}

List all exports

GET/v1/exports

Returns the practice's exports, newest first, whether they were started through the API or by an admin in Avoice. Supports pagination.

Parameters

statusenum
Only return exports with this status.queuedrunningreadyfailedcanceledexpired
created[gte]timestamp
Only return exports created at or after this time.
GET/v1/exportscurl
curl -G https://api.avoice.co/v1/exports \
  -H "Authorization: Bearer avk_live_…" \
  -d limit=5 \
  -d status=ready

Download an export

GET/v1/exports/:id/download

Redirects to a signed, time-limited URL for one archive part. The link is valid for 24 hours and only for the key that requested it; call this endpoint again for a fresh link. Archives are deleted 7 days after completion, after which this returns 410.

Parameters

partinteger
Which part to download, starting at 1. Defaults to 1.

Compare the SHA-256 of each downloaded part with the sha256 listed in the export's parts to confirm the download is intact.

GET/v1/exports/:id/downloadcurl
curl -L -o backup.part1.zip \
  "https://api.avoice.co/v1/exports/exp_01J8ZK…/download?part=1" \
  -H "Authorization: Bearer avk_live_…"
Response · 302 Foundhttp
HTTP/1.1 302 Found
Location: https://exports.avoice.co/exp_01J8ZK…/part-1.zip?sig=…
Cache-Control: no-store

Cancel or delete an export

DELETE/v1/exports/:id

Cancels a queued or running export, or deletes the stored archive of a finished one before it expires. Use it once your own copy is safely stored if you would rather not leave archives on Avoice for the full 7 days.

DELETE/v1/exports/:idcurl
curl -X DELETE https://api.avoice.co/v1/exports/exp_01J8ZK… \
  -H "Authorization: Bearer avk_live_…"
Response · 200 OKjson
{
  "id": "exp_01J8ZK4V7Q2M9D3HX6T5RWN0BC",
  "object": "export",
  "status": "canceled",
  "deleted": true
}
Archive

Archive layout

Each export is a zip archive, split into numbered parts above 20 GB. The layout follows the way your work is organised in Avoice: practice-wide data at the top, then one folder per project.

  • Structured data is JSON. Large collections, such as spec sections and audit events, are JSON Lines (.jsonl, one record per line) so they can be streamed.
  • Every record keeps the same ID it has in Avoice, and relationships are expressed by ID, never by name. That is what makes an archive restorable and comparable between backups.
  • Files are included in their original format under their original names, with their metadata and version history in index.json.
  • Specifications are exported twice. The structured sections and clauses, with full version history, are for restore and comparison. Each issued revision is also included as the PDF or DOCX that was actually issued, as the record of what went out.
  • Records deleted within your retention window appear with a deleted_at timestamp, so an incremental export tells you what was removed as well as what changed.
  • Files that live in a connected cloud drive (Google Drive, SharePoint, Dropbox) are exported as metadata and source path rather than a second copy, since you already hold them.
Archive contents
avoice-export-exp_01J8ZK…/
├── manifest.json
├── schema/                   JSON Schema per type
├── practice/
│   ├── organization.json
│   ├── users.json            name, email, role
│   ├── teams.json
│   └── settings.json
├── library/
│   ├── clauses.json          clause library, masters
│   ├── materials.json        Material Room
│   └── suppliers.json
├── audit/
│   └── events.jsonl
└── projects/{project_id}/
    ├── project.json
    ├── specifications/{specification_id}/
    │   ├── specification.json
    │   ├── sections.jsonl
    │   ├── section-versions.jsonl
    │   └── publications/     every issued revision
    │       ├── publications.json
    │       └── P01-rev-C.pdf
    ├── materials.json
    ├── schedules/
    ├── documents/
    │   ├── index.json        metadata, versions
    │   └── files/            original uploads
    ├── drawings/             register, revisions
    ├── outputs/              generated documents
    ├── qc/                   checks, findings
    ├── rfis.json
    ├── submittals.json
    └── decisions.json        decision log

The manifest

Every archive starts with manifest.json. It records what the export covered, when it was generated, how many records of each type it holds, and a SHA-256 checksum for every file. Together with the JSON Schema files in schema/, it lets you show an auditor exactly what a backup contains and prove that nothing was altered.

manifest.jsonjson
{
  "schema_version": "2026-09",
  "export_id": "exp_01J8ZK4V7Q2M9D3HX6T5RWN0BC",
  "organization_id": "clx2k9v3p0000abcd1234efgh",
  "scope": { "type": "practice", "project_ids": null },
  "since": null,
  "generated_at": "2026-09-23T02:41:12Z",
  "counts": {
    "projects": 142,
    "specifications": 318,
    "spec_sections": 20417,
    "spec_publications": 1266,
    "materials": 5381,
    "schedule_items": 41209,
    "documents": 9412,
    "drawings": 7730,
    "qc_findings": 12884,
    "rfis": 961,
    "submittals": 1418,
    "decisions": 2206,
    "audit_events": 80137
  },
  "files": [
    {
      "path": "projects/clx8r1…/documents/files/A-201 Plans.pdf",
      "size_bytes": 4812201,
      "sha256": "5b1e0c9a…7d42"
    }
  ]
}

What is included

An export covers the practice's specification, project, and library data inside your contracted retention window.

AreaIncluded
PracticePractice profile, users (name, email, role), teams, settings, practice standards
ProjectsProject records, members, status, and visibility, including archived projects
SpecificationsSpecifications, sections and clauses with every saved version, attention and TBC flags, and each issued revision. NATSPEC, Uniclass, CAWS, and CSI formats
LibraryClause library and practice masters, Material Room records, manufacturers, suppliers
MaterialsProject materials and their links to spec sections and schedules
SchedulesFinishes, FF&E, furniture, door, lighting, and custom schedules; schedules of works
DocumentsUploaded files with versions and categories; drawing register, revisions, and issue history
OutputsGenerated documents and reports, exported specifications
QA and CADrawing QC checks and findings, RFIs, submittals, project decision log
AuditSpecification edit history and export and API key events

Not included:passwords and sign-in data, email and chat content, practice management records (clients, fees, timesheets, invoices), and Avoice's own models, prompts, and regulation library.

Guides

Security

An export key can read a practice's entire body of work, so the export path is locked down accordingly.

  • Practice isolation. Every query is bound to the practice that owns the key. IDs belonging to another practice return 404, so they cannot be probed.
  • Admin control. Only practice admins can create keys. Keys can be named, given an expiry date, and revoked at any time.
  • Encryption. TLS 1.2 or higher in transit. Archives are encrypted at rest and built and stored in the same UK region as your practice data.
  • Short-lived downloads. Download links expire after 24 hours and archives are deleted after 7 days, or sooner if you delete them.
  • Audit trail. Every key event, export, and download is written to your practice audit log with key name, IP address, and time.

Scheduled backup script

A complete nightly backup in about 40 lines of Python. It starts a full practice export, waits for it to finish, downloads every part, and checks each checksum before reporting success.

  • Install the one dependency with pip install requests.
  • Set AVOICE_API_KEY to a key with read:exports, and optionally AVOICE_BACKUP_DIR.
  • Schedule it with cron or Windows Task Scheduler, for example at 02:00 every night.

For large practices, run a full export weekly and pass since set to the previous run's created_at on the other nights for a faster incremental export.

Questions or a higher limit? Email hello@avoice.co.

avoice_backup.pypython
"""Nightly Avoice backup. Run from cron, e.g.  0 2 * * *  python avoice_backup.py"""
import hashlib, os, pathlib, time
import requests

API = "https://api.avoice.co/v1"
KEY = os.environ["AVOICE_API_KEY"]            # avk_live_…
OUT = pathlib.Path(os.environ.get("AVOICE_BACKUP_DIR", "./avoice-backups"))
s = requests.Session()
s.headers["Authorization"] = f"Bearer {KEY}"

def call(method, path, **kw):
    while True:
        r = s.request(method, API + path, timeout=60, **kw)
        if r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", "60")))
            continue
        r.raise_for_status()
        return r

# 1. Start a full practice export.
export = call("POST", "/exports", json={"scope": {"type": "practice"}}).json()
print("started", export["id"])

# 2. Poll until it is ready (large practices can take a while).
while export["status"] in ("queued", "running"):
    time.sleep(60)
    export = call("GET", f"/exports/{export['id']}").json()
if export["status"] != "ready":
    raise SystemExit(f"export {export['status']}: {export['failure_reason']}")

# 3. Download every part and verify its checksum.
target = OUT / export["completed_at"][:10]
target.mkdir(parents=True, exist_ok=True)
for part in export["parts"]:
    path = target / f"{export['id']}.part{part['number']}.zip"
    digest = hashlib.sha256()
    with call("GET", f"/exports/{export['id']}/download",
              params={"part": part["number"]}, stream=True) as r, open(path, "wb") as f:
        for chunk in r.iter_content(chunk_size=1 << 20):
            f.write(chunk)
            digest.update(chunk)
    if digest.hexdigest() != part["sha256"]:
        raise SystemExit(f"checksum mismatch on part {part['number']}")
    print("saved", path)