#Is it a good starting point?

A starting point for what? 
The only obvious purpose for code like this is to provide error checking for accidentally indexing out of bounds, but this code doesn't do that.

And note that `free(array)` makes assumptions about the implementation. 
There should be an `array_free(array)` function that can be internally changed in future development without affecting any calling code.

#Can I implement my custom array with [] to read/write values to my array?

Yes, but that's exactly what you *don't* want to do.
 

The most significant problem with this design is that it returns a value that the caller can use with `[…]`.
That allows it to bypass the error checking (which I assume will eventually be added).

The type returned should be something that is incompatible with accidental misuse.
If someone calls array_set() with an argument that wasn't created by array_initialize(), you want that mistake caught at compile time, not
during critical execution time.

#Can I call it an array?

Yes, but it's not a generic array; it is an array of `(int)`.

And, note that storing a zero value in the array will break `array_size()`.

---
You're going in the right direction, but started on the wrong foot.

Define your basic data structure first, and then the rest of the design should flow naturally from that.
e.g.

    typedef struct {
        size_t size;
        size_t used;
        int *value;
    } intArray;

Now it becomes obvious that the initialization function needs to allocate `*value` and should return a pointer to an `intArray` structure:

        intArray *
    intArray_initialize(int size) {
        …
    }

Then in `intArray_set()` and `intArray_get()` add code to verify that `0 <= index < size`.

And `intArray_size()` becomes trivial:  `array->used`.

Finally, `intArray_free(intArray *array)` will need to free both `array->value` and `*array`.