# Productive API > Productive is a Professional Services Automation platform. This file is the complete resource map following https://llmstxt.org. Each entry below is a filtered OpenAPI spec URL containing full schemas, parameters, and examples for that resource group. Base URL: https://api.productive.io/api/v2/ ## Auth (required on every request) X-Auth-Token: {token} (Settings → API integrations → Generate new token) X-Organization-Id: {org_id} (numeric, from your Productive URL) Content-Type: application/vnd.api+json (required on POST/PATCH/DELETE) 401 = unauthenticated | 403 = unauthorized ## OpenAPI Spec Full YAML: https://developer.productive.io/reference/download_spec Full JSON: https://developer.productive.io/reference/download_spec?format=json Filter params — append any combination to the spec URL: ?category={slug} all endpoints in a category ?group={slug} all endpoints for one resource ?path=/api/v2/tasks all methods on a path ?path=/api/v2/tasks&method=post single operation ## Resource Groups ### Organization custom-domains: https://developer.productive.io/reference/download_spec?group=custom-domains invitations: https://developer.productive.io/reference/download_spec?group=invitations organization-memberships: https://developer.productive.io/reference/download_spec?group=organization-memberships organizations: https://developer.productive.io/reference/download_spec?group=organizations permission-sets: https://developer.productive.io/reference/download_spec?group=permission-sets sessions: https://developer.productive.io/reference/download_spec?group=sessions subsidiaries: https://developer.productive.io/reference/download_spec?group=subsidiaries users: https://developer.productive.io/reference/download_spec?group=users ### Core Resources activities: https://developer.productive.io/reference/download_spec?group=activities approval-policy: https://developer.productive.io/reference/download_spec?group=approval-policy approval-policy-assignment: https://developer.productive.io/reference/download_spec?group=approval-policy-assignment approval-workflow: https://developer.productive.io/reference/download_spec?group=approval-workflow attachments: https://developer.productive.io/reference/download_spec?group=attachments comments: https://developer.productive.io/reference/download_spec?group=comments custom-field-options: https://developer.productive.io/reference/download_spec?group=custom-field-options custom-fields: https://developer.productive.io/reference/download_spec?group=custom-fields custom-field-section: https://developer.productive.io/reference/download_spec?group=custom-field-section emails: https://developer.productive.io/reference/download_spec?group=emails memberships: https://developer.productive.io/reference/download_spec?group=memberships notifications: https://developer.productive.io/reference/download_spec?group=notifications team-memberships: https://developer.productive.io/reference/download_spec?group=team-memberships teams: https://developer.productive.io/reference/download_spec?group=teams ### Projects projects: https://developer.productive.io/reference/download_spec?group=projects ### Tasks folders: https://developer.productive.io/reference/download_spec?group=folders taskdependency: https://developer.productive.io/reference/download_spec?group=taskdependency task-lists: https://developer.productive.io/reference/download_spec?group=task-lists tasks: https://developer.productive.io/reference/download_spec?group=tasks todos: https://developer.productive.io/reference/download_spec?group=todos workflows: https://developer.productive.io/reference/download_spec?group=workflows workflow-statuses: https://developer.productive.io/reference/download_spec?group=workflow-statuses ### Docs discussions: https://developer.productive.io/reference/download_spec?group=discussions pages: https://developer.productive.io/reference/download_spec?group=pages pageversions: https://developer.productive.io/reference/download_spec?group=pageversions ### Forms survey-field-options: https://developer.productive.io/reference/download_spec?group=survey-field-options survey-fields: https://developer.productive.io/reference/download_spec?group=survey-fields survey-responses: https://developer.productive.io/reference/download_spec?group=survey-responses surveys: https://developer.productive.io/reference/download_spec?group=surveys ### Time Tracking time-entries: https://developer.productive.io/reference/download_spec?group=time-entries time-entries-bulk: https://developer.productive.io/reference/download_spec?group=time-entries-bulk time-entry-versions: https://developer.productive.io/reference/download_spec?group=time-entry-versions timers: https://developer.productive.io/reference/download_spec?group=timers timesheets: https://developer.productive.io/reference/download_spec?group=timesheets time-tracking-policies: https://developer.productive.io/reference/download_spec?group=time-tracking-policies ### Financials bankaccounts: https://developer.productive.io/reference/download_spec?group=bankaccounts bills: https://developer.productive.io/reference/download_spec?group=bills contracts: https://developer.productive.io/reference/download_spec?group=contracts deal-cost-rates: https://developer.productive.io/reference/download_spec?group=deal-cost-rates deals: https://developer.productive.io/reference/download_spec?group=deals document-styles: https://developer.productive.io/reference/download_spec?group=document-styles document-types: https://developer.productive.io/reference/download_spec?group=document-types exchange-rates: https://developer.productive.io/reference/download_spec?group=exchange-rates expenses: https://developer.productive.io/reference/download_spec?group=expenses expenses-bulk: https://developer.productive.io/reference/download_spec?group=expenses-bulk overheads: https://developer.productive.io/reference/download_spec?group=overheads prices: https://developer.productive.io/reference/download_spec?group=prices proposals: https://developer.productive.io/reference/download_spec?group=proposals purchase-orders: https://developer.productive.io/reference/download_spec?group=purchase-orders purchase-orders-bulk: https://developer.productive.io/reference/download_spec?group=purchase-orders-bulk rate-cards: https://developer.productive.io/reference/download_spec?group=rate-cards revenue-distributions: https://developer.productive.io/reference/download_spec?group=revenue-distributions salaries: https://developer.productive.io/reference/download_spec?group=salaries sections: https://developer.productive.io/reference/download_spec?group=sections service-assignments: https://developer.productive.io/reference/download_spec?group=service-assignments services: https://developer.productive.io/reference/download_spec?group=services service-type-assignments: https://developer.productive.io/reference/download_spec?group=service-type-assignments service-types: https://developer.productive.io/reference/download_spec?group=service-types taxrates: https://developer.productive.io/reference/download_spec?group=taxrates ### Invoicing automatic-invoicing-rules: https://developer.productive.io/reference/download_spec?group=automatic-invoicing-rules e-invoice-identities: https://developer.productive.io/reference/download_spec?group=e-invoice-identities e-invoice-transactions: https://developer.productive.io/reference/download_spec?group=e-invoice-transactions invoice-attributions: https://developer.productive.io/reference/download_spec?group=invoice-attributions invoices: https://developer.productive.io/reference/download_spec?group=invoices invoice-templates: https://developer.productive.io/reference/download_spec?group=invoice-templates kpd-codes: https://developer.productive.io/reference/download_spec?group=kpd-codes line-items: https://developer.productive.io/reference/download_spec?group=line-items payment-reminder-sequences: https://developer.productive.io/reference/download_spec?group=payment-reminder-sequences payments: https://developer.productive.io/reference/download_spec?group=payments ### Resource Management bookings: https://developer.productive.io/reference/download_spec?group=bookings entitlements: https://developer.productive.io/reference/download_spec?group=entitlements events: https://developer.productive.io/reference/download_spec?group=events holiday-calendars: https://developer.productive.io/reference/download_spec?group=holiday-calendars holidays: https://developer.productive.io/reference/download_spec?group=holidays placeholders: https://developer.productive.io/reference/download_spec?group=placeholders placeholder-usages: https://developer.productive.io/reference/download_spec?group=placeholder-usages ### CRM companies: https://developer.productive.io/reference/download_spec?group=companies contact-entries: https://developer.productive.io/reference/download_spec?group=contact-entries deal-statuses: https://developer.productive.io/reference/download_spec?group=deal-statuses lost-reasons: https://developer.productive.io/reference/download_spec?group=lost-reasons people: https://developer.productive.io/reference/download_spec?group=people pipelines: https://developer.productive.io/reference/download_spec?group=pipelines ### Reporting dashboards: https://developer.productive.io/reference/download_spec?group=dashboards pulses: https://developer.productive.io/reference/download_spec?group=pulses report-category: https://developer.productive.io/reference/download_spec?group=report-category reports: https://developer.productive.io/reference/download_spec?group=reports widgets: https://developer.productive.io/reference/download_spec?group=widgets ## Conventions Format: JSON:API — body: {"data":{"type":"tasks","attributes":{},"relationships":{}}} Filter: ?filter[field]=value e.g. ?filter[assignee_id]=42 Paginate: ?page[number]=1&page[size]=30 (max 200) Sort: ?sort=name or ?sort=-name (descending) Rate: 100 req/10s, 4000 req/30min; reports: 10 req/30s; HTTP 429 on excess ## Create a task curl -X POST https://api.productive.io/api/v2/tasks \ -H "X-Auth-Token: TOKEN" -H "X-Organization-Id: ORG_ID" \ -H "Content-Type: application/vnd.api+json" \ -d '{"data":{"type":"tasks","attributes":{"title":"My task"},"relationships":{"task_list":{"data":{"type":"task_lists","id":"123"}}}}}' ## Log time (time in minutes; requires person_id + service_id) curl -X POST https://api.productive.io/api/v2/time_entries \ -H "X-Auth-Token: TOKEN" -H "X-Organization-Id: ORG_ID" \ -H "Content-Type: application/vnd.api+json" \ -d '{"data":{"type":"time_entries","attributes":{"date":"2024-01-15","time":60},"relationships":{"person":{"data":{"type":"people","id":"456"}},"service":{"data":{"type":"services","id":"789"}}}}}' Time entry fields: https://developer.productive.io/reference/download_spec?group=time-entries Changelog: https://developer.productive.io/reference/changelog ## Guides ### Overview URL: https://developer.productive.io/guides/overview API endpoint is https://api.productive.io/api/v2/ This API is implemented according to [JSON API spec](https://jsonapi.org/). #### Authorization URL: https://developer.productive.io/guides/authorization To authorize yourself, add your API token to the **X-Auth-Token** header for every request. API token can be generated using Productive application, navigating to **Settings** -> **API integrations** -> **Generate new token** To access your organization data, add your organization ID to the **X-Organization-Id** header for every request. Most resources have authorization on them. If successfully authorized, you will get a response containing the resource. However, if you aren't authorized then you will be given HTTP status of 403, and an error message. ### Document Format URL: https://developer.productive.io/guides/document-format The Productive JSON Document Format represents rich text stored in Productive objects. It's very similar to [Atlassian Document Fromat (ADF)](https://developer.atlassian.com/cloud/jira/platform/apis/document/structure/). It's currently being used in the following resources: | Resource | Property | Description | | -------- | -------- | ------------------------------------------------------------------------------ | | `pages` | `body` | Productive Docs are represented by `pages` resource and it's `body` property is stored as Productive Document Format | ### JSON structure A document in Productive is a JSON object, composed of a hierarchy of nodes. There are two categories of nodes: block and inline. Block nodes define the structural elements of the document such as headings, paragraphs, lists, and alike. Inline nodes contain the document content such as text and images. Some of these nodes can take marks that define text formatting or embellishment such as centered, bold, italics, and alike. A document is ordered, that is, there's a single sequential path through it: traversing a document in sequence and concatenating the nodes yields content in the correct order. For example: ``` { "type": "doc", "content": [ { "type": "paragraph", "content": [ { "type": "text", "text": "Hello " }, { "type": "text", "text": "world", "marks": [ { "type": "strong" } ] } ] } ] } ``` Result in the text "Hello **world**".
**Nodes** Nodes have the following common properties: | Property | Required | Description | | -------- | -------- | ------------------------------------------------------------------------------ | | type | ✔ | Defines the type of block node such as `paragraph`, `table`, and alike. | | content | | in block nodes, not applicable in inline nodes An array contaning inline and block nodes that define the content of a section of the document. |
**Block nodes** Block nodes can be subdivided into: - the root `doc` node - top level nodes, nodes that can be placed directly below the root node - child nodes, nodes that have to be the child of a higher-level mode Some top-level nodes can be used as child nodes. For example, the `paragraph` node can be used at the top level or embedded within a `list` or `table`.
**Root block node:** ``` { "type": "doc", "content": [] } ```
**Top-level block nodes:** - paragraph - blockquote - heading - ol - ul - checklist - table - divider - banner
**Child block nodes:** - checklist_item - table_row - table_cell - table_header - li
**Inline nodes:** - text - image - file - mention - br
**Marks** Mark have the following properties: | Property | Required | Description | | -------- | -------- | ------------------------------------------------------------------------------ | | type | ✔ | Defines the type of mark such as code, link, and alike. | | attrs | | Further information defining attributes of the mask such as the URL in a link. | The marks types are: - link - em - strike - underline - strong - code - discussion ### Node types **Doc** The `doc` node is the root container node representing a document. ``` { "type": "doc", "content": [ { "type": "paragraph", "content": [ { "type": "text", "text": "Hello world" } ] } ] } ```
**Paragraph** The `paragraph` node is a container for a block of formatted text delineated by a carriage return. It's the equivalent of the HTML `

` tag. Paragraph is a top-level block node that can take any inline node as `content`. ``` { "type": "paragraph", "content": [ { "type": "text", "text": "Hello world" } ] } ```
**Quote** The `blockquote` node is a container for quotes. ``` { "type": "blockquote", "content": [ { "type": "text", "text": "Hello world" } ] } ```
**Heading** The `heading` represents a heading. Heading can have 3 levels. ``` { "type": "heading", "attrs": { "level": 1 }, "content": [ { "type": "text", "text": "Hello world" } ] } ```
**Ordered list** The `ol` node is a container for ordered list. List node can only contain `li` (list item) child block nodes. ``` { "type": "ol", "content": [ { "type": "li", "content": [ { "type": "paragraph", "content": [ { "type": "text", "text": "Hello world" } ] } ] } ] } ```
**Unordered list** The `ul` node is a container for unordered list. List node can only contain `li` (list item) child block nodes. ``` { "type": "ul", "content": [ { "type": "li", "content": [ { "type": "paragraph", "content": [ { "type": "text", "text": "Hello world" } ] } ] } ] } ```
**Checklist** The `checklist` node is a container for list of checkbox items. `checklist` can only contain `checklist_item` child block nodes. ``` { "type": "checklist", "content": [ { "type": "cheklist_item", "attrs": { "checked": true }, "content": [ { "type": "paragraph", "content": [ { "type": "text", "text": "Hello world" } ] } ] } ] } ```
**Table** The `table` node is a container for a table. Table node can only contain `table_row` nodes, which can contain `table_cell` or `table_header` child block nodes. ``` { "type": "table", "content": [ { "type": "table_row", "content": [ { "type": "table_cell", "attrs": { "colspan": 1, "rowspan": 1, "colwidth": null }, "content": [ { "type": "paragraph" } ] }, { "type": "table_cell", "attrs": { "colspan": 1, "rowspan": 1, "colwidth": null }, "content": [ { "type": "paragraph" } ] }, { "type": "table_cell", "attrs": { "colspan": 1, "rowspan": 1, "colwidth": null }, "content": [ { "type": "paragraph" } ] } ] }, { "type": "table_row", "content": [ { "type": "table_cell", "attrs": { "colspan": 1, "rowspan": 1, "colwidth": null }, "content": [ { "type": "paragraph" } ] }, { "type": "table_cell", "attrs": { "colspan": 1, "rowspan": 1, "colwidth": null }, "content": [ { "type": "paragraph" } ] }, { "type": "table_cell", "attrs": { "colspan": 1, "rowspan": 1, "colwidth": null }, "content": [ { "type": "paragraph" } ] } ] } ] } ```
**Divider** The `divider` node is a divider between different blocks. ``` { "type": "divider" } ```
**Banner** The `banner` node is a banner-like block element with 4 different types. Supported types are - "warning", "success", "critical", "info". ``` { "type": "banner", "attrs": { "type": "warning" }, "content": [ { "type": "paragraph", "content": [ { "type": "text", "text": "This is a banner" } ] } ] } ```
**Text** The `text` node is inline element that contains text and can have "marks" applied to it. ``` { "type": "text", "text": "hello", "marks": [ { "type": "strong" } ] } ```
**Image** The `image` node represents an inline image and supports `src`, `alt`, `title` and `width` attrs. ``` { "type": "image", "attrs": { "src": "", "alt": "some text", "title": "title.jpeg", "width": 100 } } ```
**File** The `file` node is an inline node that represents a link to an uploaded file. ``` { "type": "file", "attrs": { "url": "", "name": "file.html", "type": "xml" } } ```
**Mention** The `mention` node is an inline node that represents a reference to an object - person, task or a page. It behaves like a link and visually renders as a mention tag. Mention node supports these attrs: - `id` -> the ID of the referencing object - `type` -> type of the referencing object - `label` -> the text inside of an mention tag - `avatarUrl` -> URL of an avatar if the mention is person object
``` { "type": "mention", "attrs": { "id": "1", "type": "person", "label": "John Doe", "avatarUrl": "" } } ```
**Break** The `br` node inserts a new line in a text string. It's the equivalent to a `
` in HTML. ``` { "type": "doc", "content": [ { "type": "paragraph", "content": [ { "type": "text", "text": "Hello" }, { "type": "br" }, { "type": "text", "text": "world" } ] } ] } ``` ### Mark types **Link** The `link` mark sets a hyperlink. This mark applies to text nodes ``` { "type": "text", "text": "Productive", "marks": [ { "type": "link", "attrs": { "href": "https://productive.io", "title": "Productive" } } ] } ```
**Em** The `em` mark sets italic styling. This mark applies to `text` nodes. ``` { "type": "text", "text": "Hello world", "marks": [ { "type": "em" } ] } ```
**Strike** The `strike` mark sets strike-through styling. This mark applies to `text` nodes. ``` { "type": "text", "text": "Hello world", "marks": [ { "type": "strike" } ] } ```
**Underline** The `underline` mark sets underline styling. This mark applies to text nodes. ``` { "type": "text", "text": "Hello world", "marks": [ { "type": "underline" } ] } ```
**Strong** The `strong` mark sets strong styling. This mark applies to text nodes. ``` { "type": "text", "text": "Hello world", "marks": [ { "type": "strong" } ] } ```
**Code** The `code` mark sets code styling. This mark applies to text nodes. ``` { "type": "text", "text": "Hello world", "marks": [ { "type": "code" } ] } ```
**Discussion** The `discussion` mark highlights the text and links to a discussion feed. This mark applies to text nodes. ``` { "type": "text", "text": "world", "marks": [ { "type": "discussion", "attrs": { "discussionId": "1", "resolvedId": "" } } ] } ``` #### Content Negotiation URL: https://developer.productive.io/guides/content-negotiation `Content-Type` header must be set to `application/vnd.api+json`. While sending bulk requests, make sure to set `Content-Type` to `application/vnd.api+json; ext=bulk`. When `Content-Type` is not set as described, API will return **415** response status error. #### Filtering URL: https://developer.productive.io/guides/filtering If you would like to add filtration to your query, you can do that by setting the supported filter parameters in the following way: `?filter[person_id]=24`. In case you set filter parameter that is not supported for the query, you will get 400 status error: ``` json { "errors": [ { "status": 400, "title": "Unsupported Filter", "detail": "Filter 'undefined' is not supported on this endpoint" } ] } ``` **Filter Operations** There is also support for different filter operations. Allowed operations are: - `eq` - `not_eq` - `contains` - `not_contain` - `gt` - `gt_eq` - `lt` - `lt_eq` To use operations for filtering, define it after the param name: `?filter[person_id][not_eq]=24`. NOTE: Not all endpoints support filter operations. If an endpoint supports filter operations, it will be listed in the documentation for that endpoint. **Logical operation groups** Endpoints support logical operations as well. The structure for the logical operations are as follows: ``` # logical *operation* filter[$op]=or|and # each *operation* can have one or more *operands* denoted by index numbers filter[{index}][{operand}] # *operand* can be a direct value # Example: filter[$op]=or filter[0][name][eq]=Productive filter[1][date][gt]=2024-01-01 filter[2][date][eq]=2023-01-01 # or be another logical operation # Example: filter[$op]=or filter[0][name][eq]=Productive filter[1][$op]=and filter[1][0][date][gt]=2024-01-01 filter[1][1][date][eq]=2023-01-01 ``` **DateTime filtering** By default all values sent to date or datetime filters are parsed as `dates` (only their date component is used to do the filtering) You can get your filter value parsed as datetime by using the `filteringSkipDatetimeCastToDate` flag. To enable this feature, append the `filteringSkipDatetimeCastToDate` flag to your request header (`X-Feature-Flags`). This functionality applies **only** to `DateTime` fields (e.g., `created_at`, `updated_at`). In case it is used on a `Date` field (eg., `end_date` or `start_date`) flag will be ignored and values evaluate as `Dates`. The table below illustrates how the filter value is interpreted based on whether the `filteringSkipDatetimeCastToDate` flag is enabled or disabled | filteringSkipDatetimeCastToDate Flag | Value | Parsed as | | ---------------------------------------- | ----------------------------- | ------------ | | Disabled | 2024-09-12 | Date | | Disabled | 2024-09-12T10:15:30.000+02.00 | Date | | Enabled | 2024-09-12 | Date | | Enabled | 2024-09-12T10:15:30.000+02.00 | DateTime | Granulation of a filter with datetime value is one second. NOTE: Filter values are included in the URL and must be properly URL-encoded. For instance, the character `+` should be encoded as `%2B` and `-` as `%2D` ### Error Handling URL: https://developer.productive.io/guides/error-handling ## Possible errors 400
Used when a given query param is not supported. Possible title values: *Unsupported Filter*, *Unsupported Filter Value*, *Unsupported Sort*, *Unsupported Group* ``` json { "errors": [ { "status": 400, "title": "*one of listed values*", "detail": "*...* is not supported on this endpoint" } ] } ``` 401 ``` json { "errors": [ { "status": 401, "title": "Unauthenticated", "detail": "You are not authenticated" } ] } ``` 403 ``` json { "errors": [ { "status": 403, "title": "Access Denied", "detail": "You are not authorized to access this resource" } ] } ``` 404 ``` json { "errors": [ { "status": 404, "title": "Record Not Found", "detail": "The requested record was not found" } ] } ``` 406 ``` json { "errors": [ { "status": 406, "title": "Not Acceptable", "detail": "The request was not accepted" } ] } ``` 415 ``` json { "errors": [ { "status": 415, "title": "Unsupported Media Type", "detail": "Unsupported content type" } ] } ``` 422 ``` json { "errors": [ { "status": 422, "title": "Invalid Attribute", "detail": "Unsupported content type" } ] } ``` 500 ``` json { "errors": [ { "status": 500, "title": "Server Error", "detail": "An error occured on the server" } ] } ``` #### Sorting URL: https://developer.productive.io/guides/sorting To sort query results, you can use sort parameter, passing available sort params for the resource: `?sort=name`. All available sort params are defined separately for each resource. You can provide desired sort order using `-` sign (`?sort=-name`), where no `-` defines ascending and `-` defines descending order by the given sort parameter. If a given parameter is not supported, `Unsupported Sort` error (with status 400) will be raised: ``` json { "errors": [ { "status": 400, "title": "Unsupported Sort", "detail": "Sort by 'unsupported' is not supported on this endpoint" } ] } ``` ### FAQ URL: https://developer.productive.io/guides/faq ### General *Q: Are the API limits on the enterprise plan different from the other plans?* A: No.


*Q: Do custom fields have a dedicated API or do they use the same API as the module they are added to?* A: Custom Fields are a combination of custom field attributes and definitions of CFS through `/custom_fields` and `/custom_fields_options`.
*Q: If we use webhooks, do these have any usage limits we should be aware of?* A: No.
*Q: Is it possible to assign the responsible attribute to a team instead of a person? I did try replacing “people” with type “teams” (and also “team”) but it said the data/attributes/responsible attribute was invalid both times.* A: No. Responsible attribute (assignee) can’t be a team, only an individual (people).
### Deals (Budgets) *Q: What's the difference between a Deal and a Budget?* A: Deal and Budget are on the same `deals` endpoint. Deal in Productive is a prospective project with potential financial outcomes that is still in the sales phase. When a deal is marked as "Won", it can lead to the creation of a new project and a new budget. A budget is a concrete financial plan for a project that has been won or is already underway. A budget is necessary for invoicing and provides a detailed financial overview of a project's performance.
*Q: Can’t update budget to delivered.
I'm trying to setup an API that updates a budget to delivered. I'm using the request body which is exactly the same as the request body in the API Documentation > Deals > Update a Budget. This does not update the budget to delivered (i.e. the "delivered_on" field remains "null").* A: Send `PATCH` request to `/deals/{id}/close` to close the deal; where the `{id}` is the ID of the deal.
Request: ``` json { "data": { "type": "deals", "attributes": { "delivered_on": "2024-03-14" } } } ``` is enough to update `delivered_on`. Key takeaway:
Through PATCH request to `/deals/{id}` you can’t update `delivered_on` if the budget isn’t delivered (closed) already.
*Q: Can’t bulk update budgets and deals.
I was wondering if you have an example request for bulk insert? I created 200 budgets/deals without a problem, but I had to make 200 requests. Since the API allows only 100 requests/10s, I had to add a rate limiting on our side.* A: We do not have the option to bulk create deals/budgets, only for update and delete. Key takeaway:
For bulk actions, in the `Content-Type` header field you need to add the following: `application/vnd.api+json; ext=bulk`. This works only for updating and deleting, it does not work for creating deals/budgets.
*Q: How to fetch tentative bookings via API?* A: Apply the relevant filter on bookings endpoint: `/bookings?filter[with_draft]=true&filter[draft]=true`.
*Q: Can’t create a deal. I'm having trouble creating a deal. I’ve used the same company ID that was returned on the creation of a company, but I receive the following error message:* ``` json { "errors": [ { "status": 422, "title": "Invalid Attribute", "detail": "can not be internal company", "source": { "pointer": "data/attributes/company" } } ] } ``` A: For client deals/budgets, you need to send `2` in `deal_type_id` attribute -> `1` is used for internal deals. If you define the deal/budget as internal (`1`), we automatically override the company and set it to your internal company and that is when the validation breaks (deals can’t be created against internal companies). So, you need to send:`"deal_type_id": 2` Key takeaway:
`"deal_type_id": 1` is internal.
`"deal_type_id": 2` is client.
### Projects *Q: Request to reorder properties.
I’m retrieving data from the projects endpoint. Most data objects have 2 properties in their JSON: `id` and `type`, which appear in that order. To help us deserialize the JSON efficiently it would be really helpful if the `type` property always appeared as the first property.* A: JSON, by definition, is not a stored list of values, which means that the order is not important. Additionally, Productive API follows the OpenAPI standard: [JSON](https://jsonapi.org/). We are using built-in libraries to generate JSON responses.
### Reports *Q: Rate limit reached.
We're trying to fetch a lot of records, so we're constantly getting the Rate limit reached. Is there anything we can do better?* ``` json { "errors": [ { "status": 429, "title": "Too many requests", "detail": "Rate limit reached. Try again later", "limit": 10 } ] } ``` A: By default, we only return 30 records per page - this can be adjusted up to the max of 200 per request by adding param `page[size]=200`. This will lower the number of requests needed to fetch all the records so the customer shouldn't encounter any throttling errors from our side. See [Pagination](https://developer.productive.io/index.html#header-pagination). Explanation:
All of our `report` endpoints have a stricter rate limit of 10 requests per 30 seconds. This is unfortunately necessary as computations needed for them can be quite expensive in terms of resources so we have to protect our API from any extensive usage. See [Rate Limits](https://developer.productive.io/index.html#header-rate-limits).
### Invoices *Q: Can’t add invoice amount through create invoice method.
With the create invoice method, I see it is possible to define currency in the request but not invoice amount?* A: That should be done through the `line_items` model. For example, when you create a line item that belongs to an invoice, we will automatically update the amount on the invoice. Key takeaway:
Use `line_items` to add invoice amount.
*Q: Linking time_entry to invoice.
I'm looking for a way to attribute a `time_entry` to a specific invoice in the data from API.* A: The connection between time entries and invoices goes like this invoice -> invoice attribution -> budget -> service -> time entry, which means that you can't attach `time_entry` directly to the invoice, but through `invoice_attribution` it is linked to the budget, which then already has some services that have some time entries.
### Docs *Q: Can't find docs.* A: Docs are located on `pages` endpoint. You can create a doc via API, but can’t insert text in the doc/page via API (text in docs is in JSON format).
### Copy to accounting *Q: How to copy invoice to accounting integration with API?* A: First make sure integration existing and is valid. Integration validity and refreshing export options can be done with endpoint: `GET` -> `/integrations/{INTEGRATION_ID}/check` Next step is to set copy options on the company of the invoice. Options are different for every accounting tool. Attribute is “settings“ hash field and options are under `“exporter_options“`. Example for Xero: ![image](https://developer.productive.io/img/xero.png) `PATCH` -> `/companies/{COMPANY_ID}` ``` json { "settings": { "exporter_options": { "tax_rate": , "Tax Exempt": , "tax_type":"NONE", "xero_invoice_status":"1" } } } ``` Company name will be automaticlly matched or created with invoice bill to data. Final step is to export the invoice with action: `PATCH` -> `/invoices/{INVOICE_ID}/export`
### Importing Docs via API URL: https://developer.productive.io/guides/importing-docs-via-api Using Productive's API, you can efficiently import Docs (with text) into your account. Note that it's necessary to adapt the content, such as bullets, numbers, callouts, and headers, to fit our JSON structure when utilising JSON objects. For detailed documentation on the `document format`, refer to the Productive API Documentation guide covering the [Document Format](https://developer.productive.io/document_format.html). > Tip: To avoid potential sync issues, a Doc shouldn't be updated via API while someone has the Doc open in the interface. ### Creating New Docs To create new docs, you'll interact with the `pages` endpoint. ​ **POST Endpoint:** `POST https://api.productive.io/api/v2/pages` You must send JSON inside the `body` under `attributes`. When using Postman, JSON should be sent as a string. You can utilize `JSON.stringify()` for this purpose. > Note: If you do not see changes on your Doc after updating it, this could be because the docs-realtime system cached the previous version. It may take up to one hour for the new changes to appear, as the docs-realtime system refreshes its cache periodically. #### Pagination URL: https://developer.productive.io/guides/pagination Pagination has to be set in the following style: `?page[number]=2&page[size]=20` Where `page[number]=` is the page you want to view, and `page[size]=` is the number of resources you want to return. To check pagination settings or how many resources there are in total, check the `meta` section in the response of your request. There you can see the following:
`current_page` - 1 by default or the value you put in `page[number]`
`total_pages` - `total_count`/`page_size` rounded up
`total_count` - total number of resources you have
`page_size` - 30 by default or the value you put in `page[size]`
`max_page_size` - 200 An example of how to use pagination:
Sending `page[number]=2` and `page[size]=15` will result in seeing resources from 16 to 30 on page 2, and the total_pages number will be `total_count`/15 rounded up. #### Resource Representation URL: https://developer.productive.io/guides/resource-representation # Response format The response format is JSON API. It is a JSON object with: * a required `data` key, which contains the requested resources * an optional `meta` key, which contains information about the response * an optional `links` key, which contains links for pagination * an optional `included` key, which contains data of related resources The `data` key can contain a single resource or an array of resources. Example: ```json { "data": {}, "meta": {}, "links": {}, "included": {} } ``` # Resource A resource is an object representing a single entity in the system. It is identified by its type and ID and contains attributes and relationships. ## Attributes Attributes are the properties of a resource that are not relationships. They can be any valid JSON value. ## Relationships Relationships are references to other resources that are associated with the current resource. By default, information about these relationships is not included. ## Example: ```json { "data": { "id": "1", "type": "tasks", "attributes": { "name": "Task 1", "description": "This is a task", "due_date": "2023-01-01" }, "relationships": { "creator": { "meta": { "included": false }, }, "project": { "meta": { "included": false }, }, "parent_task": { "meta": { "included": false }, } } } } ``` # Including relationships To include relationships in the response, you must pass an `include` query parameter which contains a comma-separated list of the desired related resources: `?include=creator,project.project_manager`. This will: 1. Include the identifier data (ID and type) to the `relationships` section of the resource. 2. Include the requested relationship data in the `included` section of the response. 3. If a dot notation is used, it will include the data of the related relationship as well. If requested resources does not exist, `Unsupported Include` error (with status 400) will be raised: ``` json { "errors": [ { "status": 400, "title": "Unsupported Include", "detail": "Include 'userz' is not supported on this endpoint" } ] } ``` If `include` query parameter is not provided, the response will not include `included` section. Example: `GET http://api.productive.io/api/v2/tasks?include=creator,project.project_manager` ```json { "data": { "id": "1", "type": "tasks", "attributes": { "name": "Task 1", "description": "This is a task", "due_date": "2023-01-01" }, "relationships": { "creator": { "data": { "id": "1", "type": "people" } }, "project": { "data": { "id": "1", "type": "projects" } }, "parent_task": { "meta": { "included": false } } } }, "included": [ { "id": "1", "type": "people", "attributes": { "name": "John Doe", "email": "john@doe.com" } }, { "id": "1", "type": "projects", "attributes": { "name": "Project 1", "description": "This is a project" }, "relationships": { "project_manager": { "data": { "id": "1", "type": "people" } }, "workflow": { "meta": { "included": false } } } } ] } ``` In the example above, the request asked for the `creator` and `project.project_manager` relationships. The `included` section contains just one "person" because the same person is the creator of the task and also the project_manager of the project and is only included once. Since `parent_task` was not requested, its data is not included. Also note that to get the `workflow` relationship, the `include` query parameter must contain `project.workflow`, as `workflow` is a relationship of the `project` and not of the `task`. # Sparse fields To return a subset of fields of a resource in the response, you must pass an `fields` query parameter which contains an hash of resource types with a comma separated list of wanted fields: `?fields[people]=name,last,company`. The wanted fields can be any attribute or relationship of a resource. Fields can also be attributes of a relationship resource, not just the main resource. If requested field does not exist, backend will ignore that non-existing fields, but will work with others existing fields. It can also be used with `include` query parameter. For example: `GET http://api.productive.io/api/v2/tasks?fields[tasks]=title,creator&fields[people]=first_name,date&include=creator` results in: ```json { "data": { "id": "1", "type": "tasks", "attributes": { "title": "Task 1" }, "relationships": { "creator": { "data": { "id": "1", "type": "people" } } } }, "included": [ { "id": "1", "type": "people", "attributes": { "first_name": "John" } } ] } ``` **NOTE:** If `include` query contains a relationship that is not present in the `fields` query, the response will include the relationship in the `included` section, but you will not be able to link it to the right resource. #### API changes URL: https://developer.productive.io/guides/api-changes Changelog is located at [Changelog](openapi:changelog) You can also subscribe to our email list for API change announcements. If you use API integrations, it is recommended to subscribe and stay on track with updates. You can later unsubscribe from this email list. We will occasionally send emails related to changes in our APIs to subscribers. #### Rate Limits URL: https://developer.productive.io/guides/rate-limits The API utilizes rate limiting to control the number of requests that can be made within a specified time period. This ensures fair usage of resources and prevents abuse or overloading of the server. The rate limits are structured around `a 100 requests per 10 seconds` with additional limit of `4000 requests per 30 minutes` allowing for occasional bursts of higher request rates in short intervals. If the rate limit is exceeded, the server will respond with an appropriate HTTP status code (e.g., `429 Too Many Requests`), indicating that the client should slow down and comply with the rate limits. Special consideration is given to the reports endpoint due to its resource-intensive nature. To manage its usage effectively, additional throttling measures are implemented, allowing a maximum of `10 requests within a 30-second` timeframe. ### Working with attachments URL: https://developer.productive.io/guides/working-with-attachments ### Uploading attachments Uploading an attachment is done in **4 steps**: - 1) Create the attachment object - 2) Upload the file to S3 storage - 3) Update the attachment object with the file's storage path - 4) Link the attachment to the relevant object (e.g., tasks, comments) by updating it with the attachment_id
--- **Example**: This example will use case of attaching attachment to a comment in order to showcase all the necessary steps. **Step 1 - Creating the attachment object**: `POST https://api.productive.io/api/v2/attachments` Request body will have to specify information about the attachment; name you want to use, content type, size and attachable_type. ``` { "data": { "attributes": { "name": "file.pdf", "content_type": "application/pdf", "size": 1234, "attachable_type": "comment" }, "type": "attachments" } } ```
**Step 2 - Uploading the file to S3 storage**: Response of this request will contain `aws_policy` field with all the necessary information for file upload. ``` "aws_policy": { "key": "attachments/files/002/592/959/original/file.pdf", "success_action_status": "201", "x-amz-algorithm": "AWS4-HMAC-SHA256", "x-amz-credential": "AKIAJUS2LA6R66ZPRGVA/20230309/eu-west-1/s3/aws4_request", "x-amz-date": "20230309T091615Z", "x-amz-signature": "a5f58e21f5a9ceae407c8f183a0e8d8262a5dee4e1283b2db9fef8f975ceadbe", "x-amz-security-token": "long_token_value", "policy": "long_policy_value", "Content-Type": "application/pdf" } ``` Upload request must contain form data with all the fields from aws_policy and your file. `POST https://productive-files-production.s3.eu-west-1.amazonaws.com` Example of the request using curl: ``` curl -X POST \ -F "key=attachments/files/002/592/959/original/file.pdf" \ -F "success_action_status=201" \ -F "x-amz-algorithm=AWS4-HMAC-SHA256" \ -F "x-amz-credential=AKIAJUS2LA6R66ZPRGVA/20230309/eu-west-1/s3/aws4_request" \ -F "x-amz-date=20230309T091615Z" \ -F "Content-Type=application/pdf" \ -F "x-amz-signature=61ba2467b8ff183ab2c7793b865367a3452bb158fdcf6a96000f1897b08404ee" \ -F "x-amz-security-token=long_token_value", -F "policy=long_policy_value" \ -F "File=@path/to/file.pdf" \ https://productive-files-production.s3.eu-west-1.amazonaws.com ```
**Step 3 - Updating the attachment object with the file's storage path**: In the response of your file upload you will see an URL inside the Location tag ``` https://productive-files-production.s3.amazonaws.com/...file.pdf ``` That URL should be patched into the attachment under temp_url `PATCH https://api.productive.io/api/v2/attachments/{id}` ``` { "data": { "attributes": { "temp_url": "https://productive-files-production.s3.amazonaws.com/...file.pdf" }, "type": "attachments" } } ```
**Step 4 - Linking the attachment to the comment by updating it with the attachment_id**: `PATCH https://api.productive.io/api/v2/comments/{id}` In the body add attachment in the relationships array: ``` "attachments": { "data": [ { "type": "attachments", "id": "attachment id" } ] } ```
### Accessing attachments Accessing attachments programatically requires authentification with the API token. This is done with query parameter `token` through https://files.productive.io endpoint. For example: `GET https://files.productive.io/attachments/files/002/592/959/original/file.pdf?token=f80b89b3-16ab-4958-afdc-6979a4eb2207`
### Working with custom fields URL: https://developer.productive.io/guides/working-with-custom-fields ### Creating custom fields When working with custom fields, you should first create a new custom field on the desired type of object (which you can specify with the `customizable_type` attribute). For more information about `API requests` for creating and managing custom field objects, check out [API Documentation: Custom fields](https://developer.productive.io/custom_fields.html#custom-fields-custom-fields). For this example, we will show how to create a custom field for a deal and populate the value of this custom field for a certain deal object. First step is creating the custom field object: `POST https://api.productive.io/api/v2/custom_fields` The request body should contain information about `customizable_type`, `data_type_id`, and the `name` of the custom field. ``` { "data": { "type": "custom_fields", "attributes": { "name": "Review link", "data_type_id": 1, "customizable_type": "deals" } } } ``` After you successfully create a new custom field object, the data part of the response body will contain the `id` of the created custom field. ``` "data": { "id": "307", "type": "custom_fields", "attributes": { "created_at": "2024-05-21T02:03:56.482+02:00", "updated_at": "2024-05-21T02:03:56.482+02:00", "name": "Review link", "data_type_id": 1, "required": false, "description": null, "archived_at": null, "aggregation_type_id": null, "formatting_type_id": null, "global": true, "show_in_add_edit_views": true, "sensitive": false, "position": 1, "customizable_type": "deals" } } ```
### Filtering custom fields If you want to retrieve all custom fields, you can do so with this API request: `GET https://api.productive.io/api/v2/custom_fields` To add custom fields to objects, they must not be archived. To retrieve available custom fields, you can use this API request: `GET https://api.productive.io/api/v2/custom_fields?filter[archived]=false` And if you want to retrieve available custom fields for a specific customizable type, you can do so with this API request: `GET https://api.productive.io/api/v2/custom_fields?filter[archived]=false&filter[customizable_type]=deals` For more about filtering please check out [API Documentation: Filtering](https://developer.productive.io/index.html#header-filtering) ### Adding custom fields to the object To add a custom field to a specific object, you should first retrieve this object and update its custom_fields attribute. Remember, the custom_fields attribute on objects is a type of `hash (dictionary)`. To prevent overwriting its values, always retrieve the entire object and add a new key (custom_field_id) to the custom_fields hash. If you remove the key, the custom field value will be lost. Here is an example of the custom_fields attribute on objects such as people, deals, budgets etc.: ``` { "data": { "attributes": { ... "custom_fields": { "custom_field_id_1": "value1", "custom_field_id_2": 12, "custom_field_id_3": ["value3", "value4"], } ... } } } ``` `custom_field_id_1`, `custom_field_id_2`, and `custom_field_id_3` are the ids of the custom field objects. If the data type is a number, the value should be numerical (`12`), for others the value should be a string (`value1`). If the data type is multiple select, the value should be an array of string values (`["value3", "value4"]`). Keep in mind: For person data type, this value is a person object `id`. For select and multiple select data types, it is the `id of the custom field option` objects.
**Example** So the first step is to retrieve the deal object for which we want to update the custom field values. `GET https://api.productive.io/api/v2/deals/{id}` In the response body, we receive a custom_fields hash with already existing values. ``` { "data": { "attributes": { ... "custom_fields": { "63": [ "70" ], "156": "code_321" } ... } } } ```
If we want to add a new custom field value, we should update this custom_fields hash. Similarly, if we want to change the value or remove a value, then we only need to modify or remove that key-value pair from the hash. For example to add the custom field "Review link" to a certain deal with the desired value "https://mylink.com" (assuming its id is 307), you can use the following API request: `PATCH https://api.productive.io/api/v2/deals/{id}` with `request body`: ``` { "data": { "id": "{id}", "type": "deals", "attributes": { "custom_fields": { "63": [ "70" ], "156": "code_321", "307": "https://mylink.com" } } } } ``` ### Filtering objects by custom fields You can filter objects such as deals by their custom field values. The correct format for this would be: `GET https://api.productive.io/api/v2/deals?filter[custom_fields][custom_field_id][operation]=filter_value` For example, to filter all deals that have the Review Link custom field set as "https://mylink.com", you can make an API call like this: `GET https://api.productive.io/api/v2/deals?filter[custom_fields][307][eq]=https://mylink.com` For more information about filtering, please check out the [API Documentation: Filtering](https://developer.productive.io/index.html#header-filtering)