The DRY (Don't repeat yourself) principle applies to testing as well, but don't overdo it. Tests should be "DAMP not DRY".
...if some day I decide to change the model of my resource, like maybe adding a new field. The actual tests won't check this new field. So I'll have to update every test related to this resource...
In this case what I usually do is create a Chai custom assertion that defines a set of assertions for a specific type, e.g a Todo. 
This custom assertion is then (re)used in all relevant tests to verify that the returned object(s) actually pass the assertions for Todo, without having to repeat the same assertions in each and every test.
Here's an example:
const chai = require('chai')
const chaiHttp = require('chai-http')
const server = 'https://jsonplaceholder.typicode.com'
chai.should()
chai.use(chaiHttp)
// Define a custom assertion for 'Todo'. We expect that a Todo always 
// has an `id` property of type `Number` and a `title` property of
// type `String`.
chai.use((chai, utils) => {
  utils.addProperty(chai.Assertion.prototype, 'Todo', function () {
    this._obj.should.have.property('id')
    this._obj.should.have.property('title')
    this._obj.id.should.be.a('Number')
    this._obj.title.should.be.a('String')
  })
})
// Begin Tests
describe('Retrieve a Todo', () => {
  it('returns a single Todo by ID', () => {
    return chai.request(server)
      .get('/todos/1')
      .then(res => {
        res.should.have.status(200)
        // Use the custom assertion to check if returned object
        // passes the assertions for `Todo`.
        res.body.should.be.a.Todo
      })
  })
})
describe('Retrieve all Todos', () => {
  it('returns a list containing 200 Todos', () => {
    return chai.request(server)
      .get('/todos')
      .then(res => {
        res.should.have.status(200)
        res.body.should.be.an('Array')
        res.body.should.have.length(200)
        // Reuse the custom assertion to check if all returned objects
        // pass the assertions for `Todo`.
        res.body.forEach(todo => todo.should.be.a.Todo)
      })
  })
})
If in the future I add a new field on Todo, i.e completed, all I need to do is modify the custom assertion like so:
chai.use((chai, utils) => {
  utils.addProperty(chai.Assertion.prototype, 'Todo', function () {
    this._obj.should.have.property('id')
    this._obj.should.have.property('title')
    this._obj.should.have.property('completed')
    this._obj.id.should.be.a('Number')
    this._obj.title.should.be.a('String')
    this._obj.completed.should.be.a('Boolean')
  })
})
... what should I test in my api responses and what should I not ?
As a minimum I would check if: 
- The response HTTP status is correct.
 
- The response body has the appropriate properties and correct types for each property. 
 
- If the response is a list of items, I would check if the response body is indeed an 
Array, if it has the length I expect it to have and if each element in the Array has the correct properties and types. 
There's no "rules" here. At the end it's a risk/time decision. Maintaining a test suite takes time. If I'm building a simple todo app for my own use, I won't concern myself too much with exhaustive testing. However if I'm building a public payment server, I'd definitely want my tests to be as exhaustive as possible.